React useCallback
Hook
React useCallback
Hook 返回一个记忆化的回调函数。
可以将记忆化理解为缓存一个值,以便不需要重新计算。
这使我们能够隔离资源密集型函数,以便它们不会在每次渲染时自动运行。
useCallback
Hook 仅在其依赖项之一更新时运行。
这可以提高性能。
useCallback
和 useMemo
Hook 类似。主要区别在于 useMemo
返回一个记忆化的值,而 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
组件也会重新渲染。
为什么这不起作用?我们使用了 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
属性发生更改时才会重新渲染。