介绍
React useCallback Hook 是一个内置的 React Hook,它返回一个可以记忆的 callback 函数,可以帮助你优化组件性能。它可以避免在每次渲染时重新创建函数,因为它只在依赖项发生变化时才会更新。
语法
useCallback(callback, deps)
其中,callback
是你想要 memoize 的回调函数,deps
是一个数组,其中包含了该回调函数依赖的值。当 deps
数组中的值发生变化时,回调函数才会更新。
问题
一个使用 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>
<button onClick={increment}>Count</button> {count}
</div>
</>
);
};
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
- Todos.js
import { memo } from "react";
const Todos = ({ todos, addTodo }) => {
console.log("子组件渲染");
return (
<>
<h2>Todos</h2>
{todos.map((todo, index) => {
return <p key={index}>{todo}</p>;
})}
<button onClick={addTodo}>Add Todo</button>
</>
);
};
export default memo(Todos)
尝试运行代码并点击“Count”按钮。我们会注意到,在开发者工具控制台会打印“子组件渲染”,即使状态 todos
没有更改,Todos
组件也会重新渲染。
为什么这不起作用?照理我们使用了 memo
,因此当点赞数增加时,Todos
组件不应该重新渲染才对,因为状态 todos
和 addTodo
函数都没有更改。
每当组件重新渲染时,它的函数会被重新创建。因此,addTodo
函数实际上已经改变了。这是因为所谓的“引用相等”。对象、数组、函数这些引用型 props 在进行 diff 算法时,都使用的是 Object.is
(引用相等)比较。
对于 React 类组件的我们可以覆写 shouldCompoentUpdate
来避免不必要的组件重新渲染,然而对于 React useCallback hook,没有生命周期函数,那应该怎样来解决这个问题呢?
解决方案
我们可以使用 useCallback Hook 来避免不必要的重新创建函数。
下面我们使用 useCallback Hook来防止 Todos
组件不必要地重新渲染。修改 index.js
:
- 导入 useCallback 依赖
import { useCallback, useState } from "react";
- 使用 useCallback
const addTodo = useCallback(() => {
setTodos((t) => [...t, 'New Todo']);
}, []);
现在 Todos
组件只会在 todos
prop 更改时重新渲染。问题迎刃而解。
评论 (0)