React useCallback Hook(钩子)

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

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

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

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

useCallback 和 useMemo 钩子类似。主要区别在于 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 更改时重新渲染。

分类导航