乐趣区

关于javascript:React内部让人迷惑的性能优化策略

大家好,我卡颂。

相比 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的实现细节会单开一篇文章探讨。

退出移动版