大家好,我卡颂。
相比 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
的实现细节会单开一篇文章探讨。