大家好,我卡颂。

相比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地址

欢送退出人类高质量前端框架群,带飞

性能优化的成果

如果不思考优化策略,代码运行逻辑如下:

  1. App组件首次render,打印App render 0
  2. 子组件Child首次render,打印child render
  3. 1000ms后,setInterval回调触发,执行updateNum(1)
  4. App组件再次render,打印App render 1
  5. 子组件Child再次render,打印child render
  6. 每过1000ms,反复步骤3~5

理论咱们会发现,反复执行步骤3~5不会产生任何变动,这里显然是有优化空间的。

针对这种状况,React的确做了优化。上述Demo会顺次打印:

  1. App render 0
  2. child render
  3. App render 1
  4. child render
  5. App render 1

这里让人困惑的点在于:为什么num从0变为1后,App render 1执行了2次,而child render只执行了一次?

接下来,咱们从实践理论角度解释以上起因。

性能优化的实践

在useState文档中提到了一个名词:bailout

他指:当useState更新的state以后state一样时(应用Object.is比拟),React不会render该组件的子孙组件

留神:当命中bailout后,以后组件可能还是会render,只是他的子孙组件不会render

这是因为,大部分状况下,只有以后组件renderuseState才会执行,能力计算出state,进而与以后state比拟。

就咱们的Demo来说,只有App renderuseState执行后能力计算出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的工作流程能够简略概括为:

  1. 交互(比方点击事件useEffect)触发更新
  2. 组件树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 renderuseState执行后才会计算出新的num,进而与以后的num比拟,判断是否命中bailout

如果updateNum(1)执行后,立即计算出新的num,进而与以后的num比拟,如果相等则组件树都不会render

这种将计算state的机会提前的策略,叫eagerState(急迫的state)。

总结

综上所述,咱们的Demo是混合了这两种优化策略后的后果:

  1. App render 0(未命中策略)
  2. child render
  3. App render 1(未命中策略)
  4. child render
  5. App render 1(命中bailout
  6. (命中eagerState
  7. (命中eagerState

......

bailout的实现细节参考React组件到底什么时候render啊。

限于篇幅无限,eagerState的实现细节会单开一篇文章探讨。