React useCallback Hook

React useCallback Hook 返回一个记忆化的回调函数。

将记忆化视为缓存一个值,以便不需要重新计算。

这使我们能够隔离资源密集型函数,以便它们不会在每次渲染时自动运行。

useCallback Hook 仅在其依赖项之一更新时运行。

这可以提高性能。

useCallback 和 useMemo Hooks 类似。 主要区别在于 useMemo 返回一个记忆化的 value 而 useCallback 返回一个记忆化的 函数。 您可以在 useMemo 章节中了解更多关于 useMemo 的信息。


问题

使用 useCallback 的一个原因是防止组件重新渲染,除非其 props 已更改。

在这个例子中,你可能认为 Todos 组件不会重新渲染,除非 todos 改变:

这与 React.memo 部分中的示例类似。

实例:

index.js

import { useState } from "react";
import ReactDOM from "react-dom/client";
import Todos from "./Todos";

const App = () => {
  const [count, setCount] = useState(0);
  const [todos, setTodos] = useState([]);

  const increment = () => {
    setCount((c) => c + 1);
  };
  const addTodo = () => {
    setTodos((t) => [...t, "New Todo"]);
  };

  return (
    <>
      <Todos todos={todos} addTodo={addTodo} />
      <hr />
      <div>
        Count: {count}
        <button onClick={increment}>+</button>
      </div>
    </>
  );
};

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

Todos.js

import { memo } from "react";

const Todos = ({ todos, addTodo }) => {
  console.log("child render");
  return (
    <>
      <h2>My Todos</h2>
      {todos.map((todo, index) => {
        return <p key={index}>{todo}</p>;
      })}
      <button onClick={addTodo}>Add Todo</button>
    </>
  );
};

export default memo(Todos);

运行实例 »

尝试运行它并单击计数增量按钮。

您会注意到,即使 todos 没有更改,Todos 组件也会重新渲染。< /p>

为什么这不起作用? 我们使用的是 memo,所以 Todos 组件不应该重新渲染,因为当计数增加时,todos 状态和 addTodo 函数都不会改变。

这是因为所谓的"参照平等"。

每次重新渲染组件时,都会重新创建其功能。 正因为如此,addTodo 函数实际上发生了变化。


解决方案

为了解决这个问题,我们可以使用 useCallback hook 来防止函数被重新创建,除非必要。

使用 useCallback Hook 来防止 Todos 组件不必要地重新渲染:

实例:

index.js

import { useState, useCallback } from "react";
import ReactDOM from "react-dom/client";
import Todos from "./Todos";

const App = () => {
  const [count, setCount] = useState(0);
  const [todos, setTodos] = useState([]);

  const increment = () => {
    setCount((c) => c + 1);
  };
  const addTodo = useCallback(() => {
    setTodos((t) => [...t, "New Todo"]);
  }, [todos]);

  return (
    <>
      <Todos todos={todos} addTodo={addTodo} />
      <hr />
      <div>
        Count: {count}
        <button onClick={increment}>+</button>
      </div>
    </>
  );
};

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

Todos.js

import { memo } from "react";

const Todos = ({ todos, addTodo }) => {
  console.log("child render");
  return (
    <>
      <h2>My Todos</h2>
      {todos.map((todo, index) => {
        return <p key={index}>{todo}</p>;
      })}
      <button onClick={addTodo}>Add Todo</button>
    </>
  );
};

export default memo(Todos);

运行实例 »

现在 Todos 组件只会在 todos 属性改变时重新渲染。