useCallback 与 useMemo
useCallBack
参数:
- 一个内联回调函数
- 依赖项数组
返回值:
- 该回调函数的 memoized 版本(可以理解为该回调函数被缓存起来了)
只有当依赖项发生改变时,这个回调函数才会重新生成。
使用场景举例:
如果你需要将一个函数作为props传给子组件,并且子组件内部进行了一些优化,比如判断一下props改变时才重新渲染。那么就可以将这个函数使用 useCallback 进行缓存,传入这个函数的 memoized 版本。当父组件更新时,这个 memoized 版本的函数是不变的,因此传给子组件的函数也是不变的,子组件就不会重新渲染。
function SubCounter({ onClick, data }) {
return <button onClick={onClick}>{data}</button>;
}
const MemoSubCounter = React.memo(SubCounter);
let oldData, oldAddClick;
function Counter1() {
console.log("Counter1 render");
const [name, setName] = useState("计数器");
const [number, setNumber] = useState(0);
// 有没有后面的依赖项数组很重要,否则还是会重新渲染
const addClick = useCallback(() => {
setNumber(number + 1);
}, [number]);
console.log("addClick===oldAddClick ", addClick === oldAddClick);
oldAddClick = addClick;
return (
<div>
<p>Counter1</p>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<MemoSubCounter data={number} onClick={addClick} />
</div>
);
}
上述代码中,使用了 React.memo
优化 SubCounter 组件,生成一个新的组件,新的组件有一个特性:如果接收到的props不变,就不会重新渲染。
上面代码中,给子组件传递了一个 addClick 函数,这个函数是一个 memoized 版本,即每次父组件更新时,这个函数是不变的,除非它的依赖项 number 发生了改变。因此当我们在 input 中进行输入,通过 setName 修改 name 属性,从而触发父组件更新时,子组件收到的 addClick 函数是不变的,子组件就不会再渲染了。
useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
参数:
- 一个内联回调函数
- 依赖项数组
返回值:
- 内联回调函数的返回值的 memoized 版本
与 useCallback
同理,当需要传一些变量给子组件时,如果这个变量没有变化,子组件也不想更新,就可以使用
useMemo
生成 memoized 版本的变量传给子组件。
function Counter2() {
console.log("Counter2 render");
const [name, setName] = useState("计数器");
const [number, setNumber] = useState(0);
// 父组件更新时,这里的变量和函数每次都会重新创建,那么子组件接受到的属性每次都会认为是新的
// 所以子组件也会随之更新,这时候可以用到 useMemo
// 有没有后面的依赖项数组很重要,否则还是会重新渲染
// 如果这样生成 data, 那么每次传给子组件的 data 都是不同的
const data = { number }
// 如果这样生成 data, 那么每次传给子组件的 data 都是相同的,除非 number 发生改变
const data = useMemo(() => ({ number }), [number]);
console.log("data===oldData ", data === oldData);
oldData = data;
// 有没有后面的依赖项数组很重要,否则还是会重新渲染
const addClick = useCallback(() => {
setNumber(number + 1);
}, [number]);
console.log("addClick===oldAddClick ", addClick === oldAddClick);
oldAddClick = addClick;
return (
<div>
<p>Counter2</p>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<MemoSubCounter data={number} onClick={addClick} />
</div>
);
}