React useCallback Hook(钩子)

React useCallback 钩子返回一个 已记忆 的回调函数。

将记忆视为缓存一个值,这样就不需要重新计算该值。

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

useCallback 钩子仅在其一个依赖项更新时运行。这可以提高性能。

useCallbackuseMemo 钩子类似。主要区别在于 useMemo 返回一个 已记忆 的值,useCallback 返回一个 已记忆 的函数。

您可以在 useMemo 一章中学习有关 useMemo 的更多知识。


问题

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

在本例中,您可能认为 Todos 组件不会重新渲染,除非 Todos 发生更改:

这是一个类似于 React.memo 部分中的示例。

实例:

index.js

  1. import { useState } from "react";
  2. import ReactDOM from "react-dom";
  3. import Todos from "./Todos";
  4. const App = () => {
  5. const [count, setCount] = useState(0);
  6. const [todos, setTodos] = useState([]);
  7. const increment = () => {
  8. setCount((c) => c + 1);
  9. };
  10. const addTodo = () => {
  11. setTodos((t) => [...t, "New Todo"]);
  12. };
  13. return (
  14. <>
  15. <Todos todos={todos} addTodo={addTodo} />
  16. <hr />
  17. <div>
  18. Count: {count}
  19. <button onClick={increment}>+</button>
  20. </div>
  21. </>
  22. );
  23. };
  24. ReactDOM.render(<App />, document.getElementById('root'));

Todos.js

  1. import { useState } from "react";
  2. import ReactDOM from "react-dom";
  3. import Todos from "./Todos";
  4. const App = () => {
  5. const [count, setCount] = useState(0);
  6. const [todos, setTodos] = useState([]);
  7. const increment = () => {
  8. setCount((c) => c + 1);
  9. };
  10. const addTodo = () => {
  11. setTodos((t) => [...t, "New Todo"]);
  12. };
  13. return (
  14. <>
  15. <Todos todos={todos} addTodo={addTodo} />
  16. <hr />
  17. <div>
  18. Count: {count}
  19. <button onClick={increment}>+</button>
  20. </div>
  21. </>
  22. );
  23. };
  24. ReactDOM.render(<App />, document.getElementById('root'));

尝试运行此命令,然后单击 "增加计数" 按钮。

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

我们使用的是 memo,而当计数增加时,todos 状态和 addTodo 函数都没有改变,所以 Todos 组件不应该重新渲染。但是没有起作用。

这是因为所谓的 "引用相等"。

每次组件重新渲染时,都会重新创建其函数。因此,addTodo 函数实际上发生了更改。


解决方法

为了解决这个问题,我们可以使用 useCallback 钩子来防止函数被重新创建。

使用 useCallback 钩子可防止 Todos 组件不必要地重新渲染:

实例:

index.js

  1. import { useState, useCallback } from "react";
  2. import ReactDOM from "react-dom";
  3. import Todos from "./Todos";
  4. const App = () => {
  5. const [count, setCount] = useState(0);
  6. const [todos, setTodos] = useState([]);
  7. const increment = () => {
  8. setCount((c) => c + 1);
  9. };
  10. const addTodo = useCallback(() => {
  11. setTodos((t) => [...t, "New Todo"]);
  12. }, [todos]);
  13. return (
  14. <>
  15. <Todos todos={todos} addTodo={addTodo} />
  16. <hr />
  17. <div>
  18. Count: {count}
  19. <button onClick={increment}>+</button>
  20. </div>
  21. </>
  22. );
  23. };
  24. ReactDOM.render(<App />, document.getElementById('root'));

Todos.js

  1. import { useState, useCallback } from "react";
  2. import ReactDOM from "react-dom";
  3. import Todos from "./Todos";
  4. const App = () => {
  5. const [count, setCount] = useState(0);
  6. const [todos, setTodos] = useState([]);
  7. const increment = () => {
  8. setCount((c) => c + 1);
  9. };
  10. const addTodo = useCallback(() => {
  11. setTodos((t) => [...t, "New Todo"]);
  12. }, [todos]);
  13. return (
  14. <>
  15. <Todos todos={todos} addTodo={addTodo} />
  16. <hr />
  17. <div>
  18. Count: {count}
  19. <button onClick={increment}>+</button>
  20. </div>
  21. </>
  22. );
  23. };
  24. ReactDOM.render(<App />, document.getElementById("root"));

现在,Todos 组件将仅在 Todos prop 更改时重新渲染。

分类导航