大家好,我卡颂。
相比Vue
能够基于模版进行编译时性能优化,React
作为一个齐全运行时的库,只能在运行时谋求性能优化。
这些优化对开发者大多是无感知的,但对我的项目进行性能优化时也常令开发者困惑。比方如下代码:
function App { const [num, updateNum] = useState(0); console.log('App render', num); useEffect(() => { setInterval(() => { updateNum(1); }, 1000) }, []) return <Child/>;}function Child() { console.log('child render'); return <span>child</span>;}
挂载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
理论咱们会发现,反复执行步骤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 <Child/>;}
失常状况,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
的实现细节会单开一篇文章探讨。