React 避免不必要的更新(函数组件)

4,847 阅读3分钟

避免子组件的不必要更新

使用memo()对函数组件进行缓存,使其只有在props变化时重新执行。

使用了memo()之后导致组件不必要更新的唯一因素就只有props的改变。对于引用类型的props改变是导致组件不必要更新的罪魁祸首,因为那常常难以让人察觉。

不必要的赋值

缓存引用类型

当组件执行时,会对组件中的所有变量重新初始化。如下代码:

const myComponent = ()=>{
    const data = [{...},...]
    const [n,setN] = useState(0) 
    return (
    <>
    <button onClick={()=>setN(n+1)}>increment</button>	
    <List dataSource={data}/> // 假设List组件是由memo(List)导出
    </>
    )
}

每次点击 increment 按钮都会出发 myComponent 组件的执行,从而导致 data 的从新初始化。而 List 组件发现 dataSource属性的引用地址发生了变化就会导致组件重新执行。

使用useMemo缓存data引用的地址,有效的避免组件执行时不必要的更新引用的变量

const data = useMemo(()=>[{...},...],[])

缓存函数

至此我们发现了变量的初始化会导致组件的重新渲染,那么上面的代码中视乎还存在一个问题 <button onClick={()=>setN(n+1)}>increment</button>,这里将箭头函数直接写死在了元素上,那么每次执行都会重新创建一个 “新的” 箭头函数。如果当前的组件不是一个元素而是一个组件的话同样会导致组件的不必要执行。

使用useCallback缓存函数,只有在n变化的时候对函数更新

<button onClick={useCallback(()=>setN(n+1),[n])}>increment</button>

可读性上的问题可以通过将代码从模板中抽离解决。

什么时候加

那既然加上缓存之后就可避免子组件不必要的更新,那我今后在写函数组件的时候一股脑全都加上行不行?

接下来就会面临三个选择:

  1. 无脑全加上
  2. 省力全不加
  3. 看情况

无脑全加篇

无脑全家绝对是可以避免组件的不必要更新,避免React启动不必要的DOM Diff操作。看似是一个不错的选择,只编写的代码体验比差一点。

但是另一个角度想想每个引用都被缓存下来,会带来一定的性能损耗吧。

  1. 首先第一点缓存引用肯定是占用了一定内存空间(空间)
  2. React会将缓存下来的引用存放在数组数据结构中,每次都需要通过便利的方式找到对应引用(时间)
  3. 还需要将旧的依赖和执行组件时产生的新依赖进行比较,还有这个对应的依赖你是不是也得使用useMemo缓存一下,依赖的依赖是不是也要useMemo一下???,直到依赖是props或者useState的产物你才能停下脚步。(复杂度)

好家伙,我直接就是一个好家伙

省力省心篇

省心省力嘛你就会得到与无脑全加完全相反的结果。

仔细琢磨篇

仔细琢磨一下就会发现,我们只是想想达到防止组件不必要更新的目的,就会有如下场景是需要加的

  1. 作为props传递给下游组件的引用类型(array object function)
  2. 在其他的hooks依赖数组中引用类型
  3. 自定义hooks抛出的任何引用类型

其余的情况你爱加不加

总结

在这里仍两个链接解释为什么不要滥用memo

zhuanlan.zhihu.com/p/85969406

royi-codes.now.sh/thousand-us…