理解React Hook 之 useCallBack

前言

刚写完一篇对useMemo的理解,赶紧趁热写写我对useCallBack的理解

useMemo 和 useCallBack 的区别

简单来说,useMemo为了解决不必要重复渲染的方法,特地缓存这个方法返回的值。避免了对该方法重复的计算,代码如下:

const foo = useMemo(() => {
    // ... complex calc
    // return calc
}, [])
复制代码

而useCallBack 则同样是为了解决不必要的重复渲染,但缓存的是这个整个函数。代码和useMemo类似:

const foo = useCallBack(() => {
    // ... todo
}, [])
复制代码

使用场景

想来看看下面这段代码:

import React, { useState } from 'react';

function Comp() {
    const [dataA, setDataA] = useState(0);
    const [dataB, setDataB] = useState(0);

    const onClickA = () => {
        setDataA(o => o + 1);
    };
    
    const onClickB = () => {
        setDataB(o => o + 1);
    }
    
    return <div>
        <Cheap onClick={onClickA}>组件Cheap:{dataA}</div>
        <Expensive onClick={onClickB}>组件Expensive:{dataB}</Expensive>
    </div>
}
复制代码

分析如下:

  • Comp组件定义了两个子组件,分别是Cheap 和 Expensive,具体组件内容就不展现了。主要说明一下Cheap组件是一个简单的组件,而Expensive组件是计算相当昂贵的组件,且两个组件都有onClick事件。
  • 当组件Cheap触发onClick事件,更新了state,导致Comp组件整个重新渲染。紧接着组件Expensive组件也跟着渲染,即该组件啥子事没做,啥子属性都没变,甚至onClick函数都没动,但也跟着全局的State的改变而重新渲染,这就导致了不必要的渲染了。
  • 为了避免重复渲染导致的性能缺陷,可以看到该组件不管是属性还是点击函数,都没有任何的改变,此时就可以使用memo进行包裹整个组件:
function Expensive({ onClick, name }) {
  console.log('Expensive渲染');
  return <div onClick={onClick}>{name}</div>
}

const MemoExpensive = memo(Expensive)
...
return (
    <div>
      <Cheap onClick={onClickA}>组件Cheap:{dataA}</div>
      <MemoExpensive onClick={onClickB} name={`组件Expensive:${dataB}`} />
    </div>
)
复制代码
  • memo 的作用类似于react class 组件中pureComponent或者是shouldComponentUpdate,主要作用是对比该组件所有的属性是否一致。所以这个是性能优化很重要的一点。
  • 但是memo对比了属性,怎么对比方法的一致呢。因为当组件重新渲染的时候,onClickB也相当于重新复制,即Expensive中onClick 对比出来的结果还是不一样,依然会继续去更新Expensive组件,所以明明onClickB没有任何变化,却导致了组件的更新,这就是导致渲染性能的浪费了。这个时候就该useCallBack出场了
const onClickB =useCallBack(() => {
      setDataB(o => o + 1);
}, [])
复制代码
  • 这样,我们再第一次渲染完后,缓存到了该点击事件。其useCallBack核心源码就对比该方法和再次渲染的方法是都是一致的,如果一致,则返回旧的方法。这样就保证了前后的函数方法保持了一致了。再配合memo就不会重复渲染。
  • 再其次useCallBack的第一个函数 如果是[]空数组,则表明它包裹的函数不会被更新。如果该数组有值,则是依赖该值的变化来更新:
const onClickB =useCallBack(() => {
      setDataB(o => o + 1);
}, [params]); // param 改变了才更新改方法
复制代码

注意

既然useCallBack这么好,那要不要对所有的方法都包裹一层呢?答案是否定的,应该我们了解到useCallBack核心原理就是拿旧的方法和重新渲染定义的方法做的一个比较,来得出是否要返回新定义的方法,显然如果每个方法加上这样一层,则会导致每个方法进行不要的对比浪费,则更浪费了性能。如果使用的场景应该是对负责的组件进行不必要渲染的时候才会使用到,所以请勿滥用。

总结

关于useCallBack也理解完了,这无疑在以后使用Hook的时候更得心应手,在前端对性能的优化层面上有了更坚实的保障了。希望继续加油,死磕前端性能优化。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享