React内部让人迷惑的性能优化策略
【React内部让人迷惑的性能优化策略】大家好,我卡颂。
相比Vue
可以基于模版进行编译时性能优化,React
作为一个完全运行时的库,只能在运行时谋求性能优化。
这些优化对开发者大多是无感知的,但对项目进行性能优化时也常令开发者困惑。比如如下代码:
function App {
const [num, updateNum] = useState(0);
console.log('App render', num);
useEffect(() => {
setInterval(() => {
updateNum(1);
}, 1000)
}, [])return ;
}function Child() {
console.log('child render');
return child;
}
挂载
App
组件后,会打印几条信息呢?本文就这个
Demo
讲解React
内部的性能优化策略。在线Demo地址欢迎加入人类高质量前端框架群,带飞
性能优化的效果 如果不考虑优化策略,代码运行逻辑如下:
App
组件首次render
,打印App render 0- 子组件
Child
首次render
,打印child render - 1000ms后,
setInterval
回调触发,执行updateNum(1)
App
组件再次render
,打印App render 1- 子组件
Child
再次render
,打印child render - 每过1000ms,重复步骤3~5
针对这种情况,
React
确实做了优化。上述Demo
会依次打印:- App render 0
- child render
- App render 1
- child render
- App render 1
num
从0变为1后,App render 1
执行了2次,而child render
只执行了一次?接下来,我们从理论和实际角度解释以上原因。
性能优化的理论 在useState文档中提到了一个名词:bailout。
他指:当
useState
更新的state
与当前state
一样时(使用Object.is
比较),React
不会render
该组件的子孙组件。注意:当命中
bailout
后,当前组件可能还是会render
,只是他的子孙组件不会render
。这是因为,大部分情况下,只有当前组件
render
,useState
才会执行,才能计算出state
,进而与当前state
比较。就我们的
Demo
来说,只有App render
,useState
执行后才能计算出num
:function App {
// useState执行后才能计算出num
const [num, updateNum] = useState(0);
// ...省略
}
在useState not bailing out when state does not change #14994中,
Dan
也反复强调这一观点。那么从理论看,在我们的
Demo
中,num
从0变为1后,child render只执行了一次是可以理解的,因为App
命中了bailout
,则他的子组件Child
不会render
。但是
bailout
只针对目标组件的子孙组件,那为什么对于目标组件App
来说,App render 1
执行了2次后就不再执行了呢?实际的性能优化策略,还要更复杂些。
实际的性能优化策略
React
的工作流程可以简单概括为:- 交互(比如
点击事件
、useEffect
)触发更新 - 组件树
render
bailout
发生在步骤2:组件树开始render
后,命中了bailout
的组件的子孙组件不会render
。实际还有一种更前置的优化策略:当步骤1触发更新时,发现
state
未变化,则根本不会继续步骤2。从我们的
Demo
来说:function App {
const [num, updateNum] = useState(0);
console.log('App render', num);
useEffect(() => {
setInterval(() => {
updateNum(1);
}, 1000)
}, [])return ;
}
正常情况,
updateNum(1)
执行,触发更新。直到App render
,useState
执行后才会计算出新的num
,进而与当前的num
比较,判断是否命中bailout
。如果
updateNum(1)
执行后,立刻计算出新的num
,进而与当前的num
比较,如果相等则组件树都不会render
。这种将计算state的时机提前的策略,叫
eagerState
(急切的state
)。总结 综上所述,我们的
Demo
是混合了这两种优化策略后的结果:- App render 0(未命中策略)
- child render
- App render 1(未命中策略)
- child render
- App render 1(命中
bailout
) - (命中
eagerState
) - (命中
eagerState
)
bailout
的实现细节参考React组件到底什么时候render啊。限于篇幅有限,
eagerState
的实现细节会单开一篇文章讨论。推荐阅读
- 函数式组件与类组件有何不同
- Java的内部类总结
- react|react 也就这么回事 04 —— 元素渲染
- Java|阿里内部流出热捧Spring全线笔记全家桶,太完整了。
- react-window|react-window 源码浅析
- react中配置路由
- React Native原理之跨端通信机制
- 企业内部线上培训系统源码
- 欧盟正式公布《数据法案》、乌克兰再遭DDoS攻击、厨具巨头美亚遭攻击内部数据泄露|网络安全周报
- 记一次react前端项目打包优化的方法