Blogger Information
Blog 82
fans 0
comment 1
visits 108242
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
防止react-re-render: Why Suspense and how ?
子龙的博客搬家啦httpswwwcnblogscomZiLongZiLong
Original
578 people have browsed it

博客搬家啦
最新博客请点击这里

近期内部项目基础项目依赖升级,之前使用的路由缓存不再适用,需要一个适配方案。而在此过程中react re-render算是困扰了笔者很久。后来通过多方资料查找使用了freeze解决了此问题。本文主要论述react re-render问题一般的解决方案和freeze在react内部的实现原理。react版本17.0.2

显示不佳请移步(掘金查看)[https://juejin.cn/post/7186241311488344121]

为什么会有re-render

首先re-render发生在某个react应用需要更新其状态的时候,这个状态一般分为三类

  1. 自身state发生变化
  2. 自身props发生变化
  3. 依赖的context发生变化

    这三类更新一般都是正常的,是react应用完成其更新所必需要触发的,但是有部分re-render是非必需的,针对非必需的re-render是我们现在需要讨论的。

    讨论之前需要多说一句的是, 对于一个合理的符合react理念编排的应用,其实re-render一般花费不了多少时间,防止re-render不能为了防止去防止

减少re-render的一般措施

  1. 父组件state变化之后,除了自身render之外,其所有子组件都会发生re-render

此处re-render的直接原因是,父组件在自己render的时候,会再一次调用:

  1. React.createElement(Child, {props}, children)

之后返回的子节点上props发生了变化在begin work阶段无法走优化策略进而触发了re-render。

此处防止re-render的方法是用React.memo 或者使用useMemo包裹组件:

  1. const MemoChild2 = useMemo(() => {
  2. return <Child2 />
  3. }, [])
  4. ...
  5. return <>{MemoChild2}</>
  6. ...

诸如此类因为父组件自身state变化而引起的re-render,还有些措施就是将state下移,即那个子组件需要这个state,就将这个state下移到该组件,避免这个子组件兄弟组件的更新

  1. 第二种是在A组件内声明B组件, 此种用法性能消耗更加吓人,根本原因是,A组件每次render函数运行之后,B都是一个新组件,对应的fiber节点上的type属性更新前跟更新后就不再指向同一组件,A组件的每一次render都会导致B组件的卸载跟挂载,根本不会存在复用:
  1. function A() {
  2. const B = function() {
  3. useEffect(() => {
  4. console.log('B render')
  5. return () => {
  6. console.log('B destroy')
  7. }
  8. }, [])
  9. return <div>B</div>
  10. }
  11. return <>
  12. <div>A</div>
  13. <B />
  14. </>
  15. }

此时若A re-render:

image.png
解决此re-render的方法就是: 将B组件移出A render函数之内:

  1. const B = function() {
  2. console.log('B render')
  3. useEffect(() => {
  4. return () => {
  5. console.log('B destroy')
  6. }
  7. }, [])
  8. return <div>B</div>
  9. }
  10. function A() {
  11. console.log('A render')
  12. return <>
  13. <div>A</div>
  14. <B />
  15. </>
  16. }

此时再re-render:

image.png
可以看到B没有再destroy,如果防止re-render可以参考1

  1. 组件依赖的context发生了变化

    如果组件依赖的context 发生了变化, 那么无论useMemo或者memo,都将无法起到作用。

如何防止context变化时组件的re-render

  1. 明确原则,此时的re-render是必须的,下边讨论的都是你的组件不需要re-render的时候,可以做的措施
  2. 首先你的context.provider 的value 不能在value值本身没有变化的时候而发生变化,否则子组件都会因为自身依赖的context变化而重新render,可以做的措施是useMemo等方法将value缓存起来
  3. 在2的基础上可以尝试将不同功能的context进行分割,即使用多个context
  4. 使用freeze,freeze内部其实就是suspense实现的,代码只有几行感兴趣的可以去github看看。

why and how

  1. Why?

    因为已经找不到别的缓存策略来解决context发生变化时的re-render了, context变化时组件对应的updatelane为1, 会直接绕过beginwork阶段的优化策略。

  2. how?

  • Suspense 。
    一般搭配react.lazy 食用, 内部原理大体是这样的,首先 render阶段,其child会指向我们要加载的第一个组件,然后当直接child未加载成功时,beginwork阶段的react执行到child时会抛出一个错误,这个错误包含了我们的加载组件时写的那个promise,然后react在其then方法上会添加一个回调函数,用于更新Suspense. 随后,将下一个要遍历的fiber节点重置为Suspense,当begin work阶段再次执行到Suspense的时候,会在其child到sibling指向fallback,并将下一个要遍历的fiber节点置为fallback, 最后在组件加载成功时触发回调函数, 完成组件的加载。
    • 由以上的原理描述我们可以看到,Suspense在遇到抛出的异常时,是“不会管”自己的child节点的,而只是说会在child节点的sibiling上携带一个fallback节点, 那么基于此我们的child节点是可以保留之前的状态的,最重要的是,他会将下一个fiber节点置为fallback节点,因此也就绕过了我们child节点在后续的可能在begin work阶段触发re-render的机制
    • 那么freeze呢,就是根据某个属性,通过周期性的抛出异常,来避免了re-render

      Suspense原理参考(https://juejin.cn/post/7145450651383201822)

Statement of this Website
The copyright of this blog article belongs to the blogger. Please specify the address when reprinting! If there is any infringement or violation of the law, please contact admin@php.cn Report processing!
All comments Speak rationally on civilized internet, please comply with News Comment Service Agreement
0 comments
Author's latest blog post