react的useCallback

102 阅读3分钟

useCallback 是一个允许你在多次渲染中缓存函数的 React Hook。
在初次渲染时,useCallback 返回你已经传入的 fn 函数
在之后的渲染中, 如果依赖没有改变,useCallback 返回上一次渲染中缓存的 fn 函数;否则返回这一次渲染传入的 fn
一般来讲,useCallbackmemo会一起使用,避免当一个组件重新渲染时,reacr递归渲染所有子组件。

注意:useCallback 只应作用于性能优化。如果代码在没有它的情况下无法运行,请找到根本问题并首先修复它,然后再使用 useCallback

使用 useCallback 缓存函数仅在少数情况下有意义:

  • 将其作为 props 传递给包装在 [memo] 中的组件。如果 props 未更改,则希望跳过重新渲染。缓存允许组件仅在依赖项更改时重新渲染。
  • 传递的函数可能作为某些 Hook 的依赖。比如,另一个包裹在 useCallback 中的函数依赖于它,或者依赖于 useEffect 中的函数。

在实践中, 你可以通过遵循一些原则来减少许多不必要的记忆化

  1. 当一个组件在视觉上包装其他组件时,让它 接受 JSX 作为子元素。随后,如果包装组件更新自己的 state,React 知道它的子组件不需要重新渲染。
  2. 建议使用 state 并且不要 提升状态 超过必要的程度。不要将表单和项是否悬停等短暂状态保存在树的顶部或全局状态库中。
  3. 保持 渲染逻辑纯粹。如果重新渲染组件会导致问题或产生一些明显的视觉瑕疵,那么这是组件自身的问题!请修复这个错误,而不是添加记忆化。
  4. 避免 不必要地更新 Effect。React 应用程序中的大多数性能问题都是由 Effect 的更新链引起的,这些更新链不断导致组件重新渲染。
  5. 尝试 从 Effect 中删除不必要的依赖关系。例如,将某些对象或函数移动到副作用内部或组件外部通常更简单,而不是使用记忆化。 (4和5在看完useEffect之后再回头理解一下)

有时,你可能在记忆化回调中基于之前的 state 来更新 state。

下面的 handleAddTodo 函数将 todos 指定为依赖项,因为它会从中计算下一个 todos:

function TodoList() {
  const [todos, setTodos] = useState([]);
  const handleAddTodo = useCallback((text) => {
    const newTodo = { id: nextId++, text };
    setTodos([...todos, newTodo]);
  }, [todos]);

我们期望记忆化函数具有尽可能少的依赖,当你读取 state 只是为了计算下一个 state 时,你可以通过传递 updater function 以移除该依赖:

function TodoList() {
  const [todos, setTodos] = useState([]);
  const handleAddTodo = useCallback((text) => {
    const newTodo = { id: nextId++, text };
    setTodos(todos => [...todos, newTodo]);
  }, []); // ✅ 不需要 todos 依赖项

在这里,并不是将 todos 作为依赖项并在内部读取它,而是传递一个关于 如何 更新 state 的指示器 (todos => [...todos, newTodo]) 给 React。(这里看完useState再回来理解一下!)