react useCallback 指北
基本使用
useCallback(callback: T, deps: DependencyList): callback
把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将非常有用。
问题
useCallback 返回的函数里面缓存了什么数据
import React, { useCallback, useState } from 'react';
class Child extends React.Component {
shouldComponentUpdate(nextProps) {
return nextProps.flag !== this.props.flag;
}
render() {
console.log('Child render');
return <div>Child Count: {this.props.count}</div>;
}
}
const UseCallBack = () => {
const [count, setCount] = useState(0);
const [selfCount, setSelfCount] = useState(100);
const memoizedCallback = useCallback(() => {
console.log('count change', count, selfCount);
}, [count]);
return (
<div>
<Child count={count} flag={memoizedCallback} />
<p>self Count:{selfCount}</p>
<p onClick={() => setCount(count + 1)}>child count add</p>
<p onClick={() => setSelfCount(selfCount + 1)}>self count add</p>
<p onClick={() => memoizedCallback()}>callback click</p>
</div>
可以看到,当我们改变 selfCount
的时候,由于我们的deps里面只监听了 count
,所以返回的 memoizedCallback
是没有变化的,Child
没有 re-render
,当memoizedCallback执行的时候打印的selfCount
还是之前的状态,没有发生变化。
结论
useCallback执行返回的函数,是第一个传入的第一个函数的 memoized 版本,并且会缓存 useState 的所有值,只有在 deps 发生变化,返回值才函数才会更新,其内部的 useState的 值才会更新 。
使用场景
function Counter() {
const [count, setCount] = useState(0);
const handleIncr = () => {
setCount(c + 1);
};
return (
<div>
{count} <Child onClick={handleIncr} />
</div>
);
}
假设Child
是一个非常复杂的组件,每一次点击它,我们会递增count,从而触发组将重新渲染。因为Counter每次渲染都会重新生成handleIncr,所以也会导致Child
重新渲染,不管Child
使用了PureComponent还是使用React.memo包装。
在这里我使用useCallback去缓存我们的 handleIncr
。
function Counter() {
const [count, setCount] = useState(0);
const handleIncr = useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<div>
{count} <Child onClick={handleIncr} />
</div>
);
}
只有在count发生变化的时候才会重新生成 handleIncr
,这样虽然能避免了一些性能问题,但是如果需要监听的变量过多,比如 useCallback(fn, [a, b,c, d]) 这样就会让代码变的很乱。
其实在这里我们只要改变一下思路,在setCount
这样操作的时候,不用闭包中的变量,而是使用函数,获取最新的值。
const Child = React.memo(({ onClick }) => {
console.log('child render');
return <div onClick={onClick}>click</div>;
});
function Counter() {
const [count, setCount] = useState(0);
const handleIncr = useCallback(() => {
setCount(c => c + 1);
}, []);
return (
<div>
{count} <Child onClick={handleIncr} />
</div>
);
}
这样 handleIncr
在每次 Counter
re-render 的时候都不会改变,并且每次操作的也是最新值