揭秘React的重新渲染机制

什么是重新渲染(re-render)

注意这里的渲染(render)其实是 react 中的协调(reconciliation) 过程,对比计算虚拟 DOM 树的差异,等到提交(commit)阶段更新到视图。

渲染分两种:初次渲染和重新渲染,什么情况下会触发组件的重新渲染,主要分为两种情况:

  • 组件自身状态的变化(useStateuseReduceruseContext)
  • 父级组件引起的重新渲染(props变化也属于这种情况)

props变化并不会引起子组件重新渲染,而根本原因就是当父组件渲染时,其所有的子组件都会重新渲染。

这里会有一个问题就是,一些情况下组件的重新渲染是没必要的,比如子组件并没有使用父组件的状态作为 props 或者 props 并没有更新,但父组件的重新渲染还是会导致子组件的重新渲染。

reconciliation 是从 root 开始,但会跳过所有父节点,到 state 发生变化的组件开始往下重新渲染。

不必要的重新渲染一般情况下不是问题,其性能开销并不大;在没有明显感知的卡顿情况下,可以不必进行优化。相反使用 React 提供的 memoization 相关的api造成的性能消耗可能会大于组件的重新渲染,得不偿失。

Don't optimize prematurely!(不要过早的性能优化)

但如果出现了性能问题,比如重新渲染触发频繁或者在性能开销大的组件上,则就必须用一些手段去跳过不必要的重新渲染。

举个例子,如Content组件内部有状态进行了更新,则Content会重新渲染,以及其所有的子组件会重新渲染。但可能 Tree 其实并不需要重新渲染,且这个组件渲染耗时较大,这时就需要想办法跳过 Tree 的 re-render。

text 复制代码
       App
      /   \
  Header  Content  ←----- update
          /     \
       Tree     Table

优化手段

这里使用dan的一个例子,ExpensiveTree是比较耗时的组件,当App组件修改颜色时,会导致App重新渲染,ExpensiveTree且没有依赖父组件的状态,是不要重新渲染的。

jsx 复制代码
import { useState } from 'react';

export default function App() {
  let [color, setColor] = useState('red');
  return (
    <div>
      <input value={color} onChange={(e) => setColor(e.target.value)} />
      <p style={{ color }}>Hello, world!</p>
      <ExpensiveTree />
    </div>
  );
}

function ExpensiveTree() {
  const now = performance.now();
  while (performance.now() - now < 100) {
    // Artificial delay -- do nothing for 100ms
  }
  return <p>I am a very slow component tree.</p>;
}

下面介绍一些优化手段:

使用memo

ExpensiveTree 使用memo函数包裹,会对props进行浅比较,如果有变化才更新。

jsx 复制代码
const ExpensiveTree = memo(() => {
  const now = performance.now();
  while (performance.now() - now < 100) {
    // Artificial delay -- do nothing for 100ms
  }
  return <p>I am a very slow component tree.</p>;
});

注意使用memo是避免父组件引发的重新渲染,如果组件自身状态发生变化,则不受影响

类组件可以查看 PureComponent /shouldComponentUpdate

下放state

  • 一般情况下可通过封装组件解决,将变化的state封装到子组件,变成兄弟组件就不会相互影响了。找个方法就是要求对组件进行合理粒度的拆分和封装。
jsx 复制代码
export default function App() {
  return (
    <>
      <Form />
      <ExpensiveTree />
    </>
  );
}

function Form() {
  const [color, setColor] = useState("red");
  return (
    <>
      <input value={color} onChange={(e) => setColor(e.target.value)} />
      <p style={{ color }}>Hello, world!</p>
    </>
  );
}

const ExpensiveTree = memo(() => {
  ...
});

组件作为props

  • 将不需要渲染的组件作为 children 传递
jsx 复制代码
export default function App() {
  return (
    <ColorPicker>
      <ExpensiveTree />
    </ColorPicker>
  );
}

function ColorPicker({ children }) {
  let [color, setColor] = useState("red");
  return (
    <>
      <input value={color} onChange={(e) => setColor(e.target.value)} />
       <p style={{ color }}>Hello, world!</p>
      {children}
    </>
  );
}

color变化时,ColorPicker会重新渲染,而ExpensiveTree作为App的子组件通过props.children进行传递, 引用并没有变化。

将组件作为props.xxx 传递也是一样的,children其实可以看作特殊的props

优化手段总结

组合

  • 下放state
  • 组件作为children/props传递

使用 memo

  • 如果props 是非基本类型的,需要配合使用useMemo/useCallback将传递的值进行缓存,防止引用变化

Context 相关

jsx 复制代码
function MyPage() {
  const [theme,setTheme] = useState('dark')
  return (
    <Context.Provider value={theme}>
       <Header />/
       <Content />
    </Context.Provider>
  );
}

注意,只有在Context.Provider 重新渲染的情况下,才会去对比value的变化,如果value的值发生变化,会使消费Context的相应组件重新渲染。

在上面的例子看,使用 state 作为value,通过 setter 触发,会使MyPage重新渲染,会导致所有子组件重新渲染,不论子组件是不是使用useContext。这里可以使用上文的优化措施,避免不必要的子组件渲染。

优化手段:

  • 如果不是在根组件使用Context.Provider,父组件可能导致其重新渲染的话,可使用useMemo记住value的值,防止value的引用变化
  • 进行Context的拆分,将不变的Context向外移,将变化的Context向内移动
  • 使用selectors,通过选择需要的状态从而规避掉无关的状态改变时带来的渲染开销。

参考

相关推荐
cc蒲公英几秒前
less和sass区别
前端·less·sass
小明记账簿几秒前
利用 Less 循环高效生成多组 CSS 间距工具类
前端·css·less
请叫我欧皇i5 分钟前
免费开源!Vue2 + OpenStreetMap 打造动态地图:标记点与弹窗高级定制
前端·vue.js·开源
拾忆,想起7 分钟前
Dubbo服务依赖问题终结指南:从根因分析到系统化解决方案
微服务·性能优化·架构·dubbo·safari
大雨倾城10 分钟前
网页端和桌面端的electron通信Webview
javascript·vue.js·react.js·electron
亚洲小炫风14 分钟前
React 分页轻量化封装
前端·react.js·前端框架
yilan_n15 分钟前
【UniApp实战】手撸面包屑导航与路由管理 (拒绝页面闪烁)
前端·javascript·vue.js·uni-app·gitcode
lang2015092817 分钟前
深入解析Sentinel熔断机制
java·前端·sentinel
FrameNotWork17 分钟前
HarmonyOS 应用性能优化全指南:渲染、状态管理、线程、内存、网络一站式提升
华为·性能优化·harmonyos
Highcharts.js24 分钟前
官方文档|Vue 集成 Highcharts Dashboards
前端·javascript·vue.js·技术文档·highcharts·看板·dashboards