React 渲染性能优化的三个方向
- 减少计算的量。 -> 对应到 React 中就是减少渲染的节点 或者 降低组件渲染的复杂度
- 利用缓存。-> 对应到 React 中就是如何避免重新渲染,利用函数式编程的 memo 方式来避免组件重新渲染
- 精确重新计算的范围。 对应到 React 中就是绑定组件和状态关系, 精确判断更新的'时机'和'范围'. 只重新渲染'脏'的组件,或者说降低渲染范围
减少渲染的节点/降低渲染计算量(复杂度)
首先从计算的量上下功夫,减少节点渲染的数量或者降低渲染的计算量可以显著的提高组件渲染性能。
不要在渲染函数都进行不必要的计算
比如不要在渲染函数(render)中进行数组排序、数据转换、订阅事件、创建事件处理器等等. 渲染函数中不应该放置太多副作用
减少不必要的嵌套
有些团队是重度的 styled-components 用户,其实大部分情况下我们都不需要这个玩意,比如纯静态的样式规则,以及需要重度性能优化的场景。除了性能问题,另外一个困扰我们的是它带来的节点嵌套地狱(如上图)。
所以我们需要理性地选择一些工具,比如使用原生的 CSS,减少 React 运行时的负担.
一般不必要的节点嵌套都是滥用高阶组件/RenderProps 导致的。所以还是那句话‘只有在必要时才使用 xxx’。 有很多种方式来代替高阶组件/RenderProps,例如优先使用 props、React Hooks
虚拟列表
虚拟列表是常见的‘长列表'和'复杂组件树'优化方式,它优化的本质就是减少渲染的节点。
虚拟列表只渲染当前视口可见元素。
虚拟列表常用于以下组件场景:
- 无限滚动列表,grid, 表格,下拉列表,spreadsheets
- 无限切换的日历或轮播图
- 大数据量或无限嵌套的树
- 聊天窗,数据流(feed), 时间轴
- 等等
惰性渲染
惰性渲染的初衷本质上和虚表一样,也就是说我们只在必要时才去渲染对应的节点。
举个典型的例子,我们常用 Tab 组件,我们没有必要一开始就将所有 Tab 的 panel 都渲染出来,而是等到该 Tab 被激活时才去惰性渲染。
还有很多场景会用到惰性渲染,例如树形选择器,模态弹窗,下拉列表,折叠组件等等。
避免重新渲染
减少不必要的重新渲染也是 React 组件性能优化的重要方向. 为了避免不必要的组件重新渲染需要在做到两点:
- 保证组件纯粹性。即控制组件的副作用,如果组件有副作用则无法安全地缓存渲染结果
- 通过shouldComponentUpdate生命周期函数来比对 state 和 props, 确定是否要重新渲染。对于函数组件可以使用React.memo包装
另外这些措施也可以帮助你更容易地优化组件重新渲染:
不要滥用 Context
其实 Context 的用法和响应式数据正好相反。笔者也看过不少滥用 Context API 的例子, 说到底还是没有处理好‘状态的作用域问题’.
首先要理解 Context API 的更新特点,它是可以穿透React.memo或者shouldComponentUpdate的比对的,也就是说,一旦 Context 的 Value 变动,所有依赖该 Context 的组件会全部 forceUpdate.
这个和 Mobx 和 Vue 的响应式系统不同,Context API 并不能细粒度地检测哪些组件依赖哪些状态,所以说上节提到的‘精细化渲染’组件模式,在 Context 这里就成为了‘反模式’.