useCallback
useCallback 允许我们缓存一个函数。当再次渲染的时候,返回上一次的函数而不是重新定义。
语法
const cachedFn = useCallback(fn, dependencies)
fn
: 缓存的函数。dependencies
: 依赖项。当依赖项变化时,重新计算结果。
使用场景
- 防止组件重新渲染
当我们传给子组件的属性有函数的时候,比如下面这个例子:
import React, { useState, memo, useMemo, useCallback } from "react";
const Hello = memo(function Hello({ text, onClick }) {
console.log("子组件重新渲染");
return <h1 onClick={onClick}>{`hello ${text}!`}</h1>;
});
export default function App() {
const [count, setCount] = useState(0);
const [text, setText] = useState("");
const onAddCount = () => {
setCount((count) => count + 1);
};
const onChangeText = () => {
setText("world");
};
return (
<>
<span>{count}</span>
<button onClick={onAddCount}>+1</button>
<Hello text={text} onClick={onChangeText} />
</>
);
}
当我们点击 count 会造成子组件的渲染,这是因为 onChangeText
是引用类型,每次父组件渲染,它都被重新定义。这导致了每次 props 都发生了变化。我们可以使用 useCallback
来缓存 onChangeText
:
// 使用useCallback来缓存onChangeText
const onChangeText = useCallback(() => {
setText("world");
}, []);
使用 useMemo 也可以实现相同的结果,只不过需要再多包一层函数:
// 使用useMemo缓存onChangeText
const onChangeText = useMemo(() => {
return () => {
setText("world");
};
}, []);
- 优化自定义 hook 如果您正在编写自定义 Hook,建议将它返回的任何函数包装到 useCallback:
function useRouter() {
const { dispatch } = useContext(RouterStateContext);
const navigate = useCallback(
(url) => {
dispatch({ type: "navigate", url });
},
[dispatch]
);
const goBack = useCallback(() => {
dispatch({ type: "back" });
}, [dispatch]);
return {
navigate,
goBack,
};
}
这确保了 Hook 的使用者可以在需要时优化他们自己的代码。
争议
有人认为应当给所有的函数包上 useCallback, 我并不认同。主要是出于以下两个考虑:
- 使用 useCallback 后代码可读性变差
- 创建一个函数的性能消耗几乎可以忽略不计,不应作为优化点