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 更改时重新渲染。