React useCallback Hook(钩子)
React useCallback 钩子返回一个 已记忆 的回调函数。
将记忆视为缓存一个值,这样就不需要重新计算该值。
这可以让我们隔离资源密集型函数,以便它们不会在每次渲染时自动运行。
useCallback 钩子仅在其一个依赖项更新时运行。这可以提高性能。
useCallback 和 useMemo 钩子类似。主要区别在于 useMemo 返回一个 已记忆 的值,useCallback 返回一个 已记忆 的函数。您可以在 useMemo 一章中学习有关 useMemo 的更多知识。
问题
使用 useCallback 的一个原因是防止组件重新呈现,除非其 props 已更改。
在本例中,您可能认为 Todos 组件不会重新渲染,除非 Todos 发生更改:
这是一个类似于 React.memo 部分中的示例。
实例:
index.js
import { useState } from "react";import ReactDOM from "react-dom";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></>);};ReactDOM.render(<App />, document.getElementById('root'));
Todos.js
import { useState } from "react";import ReactDOM from "react-dom";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></>);};ReactDOM.render(<App />, document.getElementById('root'));
尝试运行此命令,然后单击 "增加计数" 按钮。
您将注意到,即使 todos 没有更改,Todos 组件也会重新渲染。
我们使用的是 memo,而当计数增加时,todos 状态和 addTodo 函数都没有改变,所以 Todos 组件不应该重新渲染。但是没有起作用。
这是因为所谓的 "引用相等"。
每次组件重新渲染时,都会重新创建其函数。因此,addTodo 函数实际上发生了更改。
解决方法
为了解决这个问题,我们可以使用 useCallback 钩子来防止函数被重新创建。
使用 useCallback 钩子可防止 Todos 组件不必要地重新渲染:
实例:
index.js
import { useState, useCallback } from "react";import ReactDOM from "react-dom";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></>);};ReactDOM.render(<App />, document.getElementById('root'));
Todos.js
import { useState, useCallback } from "react";import ReactDOM from "react-dom";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></>);};ReactDOM.render(<App />, document.getElementById("root"));
现在,Todos 组件将仅在 Todos prop 更改时重新渲染。