防止react-re-render: Why Suspense and how ?
近期内部项目基础项目依赖升级,之前使用的路由缓存不再适用,需要一个适配方案。而在此过程中react re-render算是困扰了笔者很久。后来通过多方资料查找使用了freeze解决了此问题。本文主要论述react re-render问题一般的解决方案和freeze在react内部的实现原理。react版本17.0.2
显示不佳请移步(掘金查看)[https://juejin.cn/post/7186241311488344121]
为什么会有re-render
首先re-render发生在某个react应用需要更新其状态的时候,这个状态一般分为三类
自身state发生变化
自身props发生变化
依赖的context发生变化
这三类更新一般都是正常的,是react应用完成其更新所必需要触发的,但是有部分re-render是非必需的,针对非必需的re-render是我们现在需要讨论的。
讨论之前需要多说一句的是, 对于一个合理的符合react理念编排的应用,其实re-render一般花费不了多少时间,防止re-render不能为了防止去防止
减少re-render的一般措施
父组件state变化之后,除了自身render之外,其所有子组件都会发生re-render
此处re-render的直接原因是,父组件在自己render的时候,会再一次调用:
React.createElement(Child, {props}, children)
之后返回的子节点上props发生了变化在begin work阶段无法走优化策略进而触发了re-render。
此处防止re-render的方法是用React.memo 或者使用useMemo包裹组件:
const MemoChild2 = useMemo(() => { return <Child2 /> }, []) ... return <>{MemoChild2}</> ...
诸如此类因为父组件自身state变化而引起的re-render,还有些措施就是将state下移,即那个子组件需要这个state,就将这个state下移到该组件,避免这个子组件兄弟组件的更新
第二种是在A组件内声明B组件, 此种用法性能消耗更加吓人,根本原因是,A组件每次render函数运行之后,B都是一个新组件,对应的fiber节点上的type属性更新前跟更新后就不再指向同一组件,A组件的每一次render都会导致B组件的卸载跟挂载,根本不会存在复用:
function A() { const B = function() { useEffect(() => { console.log('B render') return () => { console.log('B destroy') } }, []) return <div>B</div> } return <> <div>A</div> <B /> </>}
此时若A re-render:
解决此re-render的方法就是: 将B组件移出A render函数之内:
const B = function() { console.log('B render') useEffect(() => { return () => { console.log('B destroy') } }, []) return <div>B</div>}function A() { console.log('A render') return <> <div>A</div> <B /> </>}
此时再re-render:
可以看到B没有再destroy,如果防止re-render可以参考1
组件依赖的context发生了变化
如果组件依赖的context 发生了变化, 那么无论useMemo或者memo,都将无法起到作用。
如何防止context变化时组件的re-render
明确原则,此时的re-render是必须的,下边讨论的都是你的组件不需要re-render的时候,可以做的措施
首先你的
context.provider
的value 不能在value值本身没有变化的时候而发生变化,否则子组件都会因为自身依赖的context变化而重新render,可以做的措施是useMemo等方法将value缓存起来在2的基础上可以尝试将不同功能的context进行分割,即使用多个context
使用freeze,freeze内部其实就是suspense实现的,代码只有几行感兴趣的可以去github看看。
why and how
Why?
因为已经找不到别的缓存策略来解决context发生变化时的re-render了, context变化时组件对应的updatelane为1, 会直接绕过beginwork阶段的优化策略。
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)
最新文章
- 摸索Tableau
- Javascript跨域
- git命令详解(转)
- implicit operator
- paip.常用汉字形声字大全3500字
- 《深入.NET平台和C#编程》--题型释疑
- 每日一九度之 题目1040:Prime Number
- SVM中图像常用的HOG特征描述及实现
- Web服务器与Web系统发布
- 实用项目管理前台框架:EasyUI,ExtJs
- [IE编程] 多页面基于IE内核浏览器的代码示例
- vim中不能使用“+y拷贝
- HID Boot device.
- Android 涂鸦最佳实践
- MYSQL:SQL中Group By的使用
- TC358743XBG:HDMI转MIPI CSI参考设计
- 报错信息 The jsp:param action must not be used outside the jsp:include, jsp:forward, or jsp:params elements 的原因及解决办法
- 基于 Django的Ajax实现 文件上传
- python与mysql交互中的各种坑
- poj 2886 线段树的更新+反素数