一. React.memo
使用场景:一个App组件里有m和n数据。还有一个Child子组件,它接受一个m作为它的外部数据。当点击把n+1的按钮时,我们知道,App会再次执行,那么Child子组件也会再次执行吗?
function App() {
const [n, setN] = React.useState(0);
const [m, setM] = React.useState(0);
const onClick = () => {
setN(n + 1);
};
return (
<div className="App">
<div>
<button onClick={onClick}>update n {n}</button>
</div>
<Child data={m}/>
</div>
);
}
function Child(props) {
console.log("child 执行了");
console.log('假设这里有大量代码')
return <div>child: {props.data}</div>;
}
通过在Child函数组件里打log,我们发现,改变n,Child也会执行一次。可是明明Child这个组件只依赖m,n变化它为什么要跟着执行呢?怎么解决这个问题?
就可以使用React.memo封装Child
const Child2 = React.memo(Child);
把Child放在React.memo里,得到一个Child2,Child2是Child优化之后的函数。在App里渲染这个Child2
结果:改变n,Child不执行了,只有改变它依赖的外部数据m时,才会执行。
使代码更简洁:直接把匿名函数组件放在memo里作为参数,得到Child组件。
const Child = React.memo((props)=>{
console.log("child 执行了");
console.log('假设这里有大量代码')
return <div>child: {props.data}</div>;
});
结论
React默认会有多余的render,当改变组件里的数据时,由于组件本身会再次执行,就导致这个组件的子组件也会再次执行。
React.memo使得一个组件只有在它依赖的props变化时,才会执行。
二. memo有一个bug
如果子组件接受了一个函数props,这个函数在父组件里定义,给子组件传进去。当我改变父组件里的n时,App()肯定会再次执行。就会导致定义在里边的这个函数也执行了。
function App() {
const [n, setN] = React.useState(0);
const [m, setM] = React.useState(0);
const onClick = () => {
setN(n + 1);
};
const onClickChild=()=>{} // 再次执行时,虽然函数内容一样,但是地址不同了
return (
<div className="App">
<div>
<button onClick={onClick}>update n {n}</button>
</div>
<Child data={m} onClickChild={onClickChild}/>
</div>
);
}
const Child = React.memo((props)=>{
console.log("child 执行了");
return <div onClick={props.onClickChild}>child: {props.data}</div>;
});
结果:点击按钮,改变n。Child明明不依赖n,但是Child也执行了。
就是因为App再次执行时,onClickChild这个函数也被重新定义了,虽然内容不会变,但是地址不同了,React就认为这个函数变了。Child接受的这个props变了,自然就会再次执行。
怎么办呢?能不能让这个函数在某种情况下,不要重新生成,继续使用上一次的缓存结果
三. 用useMemo,实现函数重用
const onClickChild = useMemo(() => {
return () => {};
}, [m]);
把这个函数经过useMemo优化。useMemo接受两个参数,第一个参数是一个返回函数的函数,返回的就是该函数本来的逻辑。第二个参数是依赖,表示只有当m变化时,才重新生成函数。如果依赖的m没变,就复用上一次的缓存,不生成新的函数。
点击n+1的按钮后,发现Child组件没有执行了。因为我们使用useMemo规定了,只有当m变化时,才重新生成props函数。m没变,即使App再次执行,这个函数也不会生成一个新的。
useMemo经常用来缓存一些在两次新旧组件迭代的时候,希望使用上一次的值
有时要缓存的,也就是return 的不一定是函数,也可以是对象
useMemo的第一个参数:()=> value,没有形参,返回一个value。第二个参数:依赖[m,n]。只有当依赖变化时,才会计算新的value。依赖没变,就重用之前的value
useCallback
useMemo第一个参数如果返回函数,写的太复杂了。这时可以用useCallBack代替
const onClickChild = useCallback(
() => {}
, [m]);
不用套外边的函数了,效果和useMemo完全一样。