useCallback

  在 React 中,组件的重新渲染是一个重要的性能考虑因素。随着我们应用程序的复杂性增加,经常因为传递给子组件的函数变更而导致不必要的重新渲染(由于函数变化了,此时就算组件用了 memo ,因为函数实际变化了,这个 memo 就会失效了)。为了避免这种情况,React 提供了一个另一个 Hook 函数 **useCallback**。它可以缓存函数实例的 React Hook,避免在每次渲染时都重新创建函数,从而提高性能。

基本概念

useCallback 是 React 的一个 Hook,它用于缓存一个函数,只有在依赖项发生变化时,才会返回新的函数实例。这个 Hook 主要用于避免函数的重新创建,特别是在函数作为子组件的 props 或者在依赖项数组中时,能够有效减少不必要的渲染。

基本语法

1
2
3
4
5
6
const memoizedCallback = useCallback(
() => {
// 函数体
},
[dependencies], // 只有当依赖项变化时,才会重新创建函数
);

使用场景

  1. 将函数作为 props 传递给子组件

    如果函数频繁地作为 props 传递给子组件,使用 useCallback 可以避免每次父组件渲染时,子组件接收到新的函数引用,避免不必要的渲染。

  2. 优化依赖关系复杂的 useEffectuseMemo

    在某些场景下,useEffectuseMemo 可能依赖于某些函数。使用 useCallback 可以确保这些函数不会在每次渲染时创建新的实例,避免了额外的依赖变化,确保渲染的稳定性。

使用示例

避免子组件重新渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import React, { useState, useCallback } from 'react';

// 子组件
const Child = React.memo(({ onClick }) => {
console.log('Child rendered');
return <button onClick={onClick}>Click Me</button>;
});

const Parent = () => {
const [count, setCount] = useState(0);

// 使用 useCallback 来缓存回调函数
const increment = useCallback(() => {
setCount((prevCount) => prevCount + 1);
}, []); // 依赖项为空,表示回调函数只会在第一次渲染时创建

return (
<div>
<Child onClick={increment} />
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Change Count</button>
</div>
);
};

export default Parent;

在这个例子中,Child 组件被包裹在 React.memo 中,只有当 onClick 这个 prop 变化时才会重新渲染。这里的 useCallback 确保了 increment 函数只有在 Parent 组件第一次渲染时创建,并且 increment 函数不会在每次 Parent 组件重新渲染时重新创建。这样,Child 组件就不会因为 Parent 渲染而重新渲染,避免了不必要的渲染开销。

useEffect 中使用 useCallback

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import React, { useState, useEffect, useCallback } from 'react';

const FetchDataComponent = () => {
const [data, setData] = useState(null);
const [fetching, setFetching] = useState(false);

const fetchData = useCallback(async () => {
setFetching(true);
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
const result = await response.json();
setData(result);
setFetching(false);
}, []); // 依赖为空,只有组件首次渲染时,fetchData 才会创建

useEffect(() => {
fetchData(); // 使用 useCallback 确保 fetchData 不会在每次渲染时重新创建
}, [fetchData]);

return (
<div>
{fetching ? <p>Loading...</p> : <pre>{JSON.stringify(data, null, 2)}</pre>}
</div>
);
};

export default FetchDataComponent;

在这个例子中,fetchData 是一个在 useEffect 中使用的函数。如果不使用 useCallback,每次 Parent 组件渲染时,fetchData 都会被重新创建,导致 useEffect 重新执行。通过 useCallback,我们确保 fetchData 只有在第一次渲染时创建,从而避免了 useEffect 的不必要调用。

useCallbackmemo

使用

useCallbackReact.memo 常常是配合使用的。useCallback 缓存函数实例,而 React.memo 用于缓存组件的渲染结果。两者结合使用时,可以有效避免函数和组件的无意义渲染,提升性能。

区别

  • **useCallback**:主要用于缓存函数实例。它返回的是一个函数,该函数只有在其依赖项变化时才会更新。
  • **useMemo**:主要用于缓存计算结果。它返回的是计算结果,并且只有在其依赖项变化时,才会重新计算值。

两者的核心区别在于:useCallback 是用来缓存函数本身,useMemo 是用来缓存计算结果。

作者

Fu9Zhou

发布于

2025-04-28

许可协议