React性能优化

150 阅读4分钟

React 优化会从渲染控制、加载优化、海量数据、细节调整四个方向入手

渲染

从调度更新任务到调和 fiber,再到浏览器渲染真实 DOM,每一个环节都是渲染的一部分,至于对于每个环节的性能优化,React 在底层已经处理了大部分优化细节,包括设立任务优先级、异步调度、diff算法、时间分片都是 React 为了提高性能,提升用户体验采取的手段。所以,开发者只需要告诉 React 哪些组件需要更新,哪些组件不需要更新。于是,React 提供了 PureComponent,shouldComponentUpdated,memo 等API优化手段。

几种控制render的方法

主要有以下两种方式:

  • 1.从父组件直接隔断子组件的渲染,经典的就是 useMemo 和 memo,缓存 element 对象。
  • 2.是组件从自身来控制是否 render ,比如:PureComponent ,shouldComponentUpdate 。

1.memo:类组件对比props.numberA !== this.state.numberA,去cloneElement,hooks就用useMemo

useMemo原理:

useMemo 会记录上一次执行 create 的返回值,并把它绑定在函数组件对应的 fiber 对象上,只要组件不销毁,缓存值就一直存在,但是 deps 中如果有一项改变,就会重新执行 create ,返回值作为新的值记录到 fiber 对象上。

加载

异步渲染

Suspense 让组件‘等待’异步操作,异步请求结束后在进行组件的渲染

Suspense 用法

Suspense 是组件,有一个 fallback 属性,用来代替当 Suspense 处于 loading 状态下渲染的内容,Suspense 的 children 就是异步组件。

传统模式:挂载组件-> 请求数据 -> 再渲染组件。
异步模式:请求数据-> 渲染组件。

那么异步渲染相比传统数据交互相比好处就是:

  • 不再需要 componentDidMount 或 useEffect 配合做数据交互,也不会因为数据交互后,改变 state 而产生的二次更新作用。
  • 代码逻辑更简单,清晰。

动态加载(懒加载)

现在的 Suspense 配合 React.lazy 可以实现动态加载功能。

React.lazy 先来看一下基本使用:

const LazyComponent = React.lazy(() => import('./test.js'))

export default function Index(){
   return <Suspense fallback={<div>loading...</div>} >
       <LazyComponent />
   </Suspense>
}
  • 用 React.lazy 动态引入 test.js 里面的组件,配合 Suspense 实现动态加载组件效果。这样很利于代码分割,不会让初始化的时候加载大量的文件。

渲染错误边界

React 组件渲染过程如果有一个环节出现问题,就会导致整个组件渲染失败,那么整个组件的 UI 层都会显示不出来,这样造成的危害是巨大的,如果越靠近 APP 应用的根组件,渲染过程中出现问题造成的影响就越大,有可能直接造成白屏的情况。

类组件用 componentDidCatch 和tDerivedStateFromError(){ return { hasError:true } }

处理大量数据

于大量数据的处理方案,对于项目中大量数据通常存在两种情况:

  • 第一种就是数据可视化,比如像热力图,地图,大量的数据点位的情况。 这种用时间分片去处理,用宏任务模拟或者requestIdleCallback方法去请求浏览器渲染帧的空闲时间去执行我们的分片数量,这样多几次渲染直到渲染完毕
  • 第二种情况是长列表渲染,可与做虚拟列表(视图区 + 缓冲区 + 虚拟区),具体实现思路。 通过 useRef 获取元素,缓存变量。 useEffect 初始化计算容器的高度。截取初始化列表长度。这里需要 div 占位,撑起滚动条。 通过监听滚动容器的 onScroll 事件,根据 scrollTop 来计算渲染区域向上偏移量, 这里需要注意的是,当用户向下滑动的时候,为了渲染区域,能在可视区域内,可视区域要向上滚动;当用户向上滑动的时候,可视区域要向下滚动。 通过重新计算 end 和 start 来重新渲染列表。

什么时候需要注意优化

但是对于以下情况,值得开发者注意,需要采用渲染节流:

  • 1.据可视化的模块组件(展示了大量的数据),这种情况比较小心因为一次更新,可能伴随大量的 diff ,数据量越大也就越浪费性能
  • 2.有大量表单的页面,React 一般会采用受控组件的模式去管理表单数据层,表单数据层完全托管于 props 或是 state ,而用户操作表单往往是频繁的,需要频繁改变数据层,所以很有可能让整个页面组件高频率 render 。
  • 3.是越是靠近 app root 根组件越值得注意,根组件渲染会波及到整个组件树重新 render

一些开发中的细节问题

  • 组件颗粒化单独抽离组件,独自管理自己的数据层,这样可以让 state 改变,波及的范围更小。

小细节

  1. input事件输入防抖
  2. 滚动事件节流
  3. 动态添加动画类名来做动画
  4. 及时清除定时器/延时器/监听器
  5. 用ref去代替state保存不需要引起render的变量