总结一下常用的RN优化的一些经验手段
减少re-render
1.shouldComponentUpdate
先了解下RN的生命周期
当组件的state和props发生改变后,re-render过程会被触发。这个阶段,可以借助shouldComponentUpdate来避免不必要的render,从而提升性能。
1 | class Button extends React.Component { |
大部分情况下,可以使用React.PureComponent来替代手写shouldComponentUpdate。但它只进行浅比较,所以当 props 或者 state 某种程度是可变的话,浅比较会有遗漏,那你就不能使用它了。所以涉及数据嵌套层级过多时,比如说你 props 传入了一个两层嵌套的 Object,这时候 shouldComponentUpdate 就很为难了
2.React.memo
另一个选择就是使用React新加入的一个能力,专门针对函数组件的高阶组件:React.memo
1 | const memoButton = React.memo(function myComponent(props) { |
值得注意的是,默认情况下React.memo是进行浅比较的。因为它的实现就是高阶组件,在原有的组件进行了一层封装。当然,如果想和shouldComponentUpdate一样加入比较,可以在使用时候传入比较函数:
1 | function Button(props) { |
3.React.PureComponent
官方文档,举例:
1 | class PureButton extends React.PureComponent { |
React.PureComponent在组件更新前对props和state进行一次浅比较,当数据有多层嵌套时候,并不是很适用。这种情况建议:
- 将组件拆分为更小粒度的组件,传入数据尽量不包含嵌套;组件内部统一用PureComponent进行渲染
- 使用Immutable.js来配合PureComponent,进行渲染优化。见有赞实践
…
总之,这部分也要充分结合业务需求,来进行定制化的优化。
4. React.useMemo和React.useCallback
在React 16版本后,引入了useMemo和useCallback,同样可以避免每次渲染时都进行的高开销计算。
1 | //返回一个memoized值,把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。 |
5. redux状态管理使用
redux作为React的状态管理,可以对项目数据提供store。但是随项目复杂度提升,引用的组件和依赖的redux状态越来越多,这会导致一个依赖redux子状态改动,会使整个页面re-render,即使这个字状态只占页面的很小一部分。
应对这种情况,需要将redux的包装由page层级改为组件层级,这样就会把组件重回引起的性能影响控制到最小:
减少渲染压力
RN的布局系统底层依赖Yoga这个跨平台的布局库,将虚拟DOM映射到原生的布局节点上的。但是这不意味着每个Virtual DOM会映射一个真实DOM。来看这个案例:
1 | render() { |
使用react-devtools可以看到页面的嵌套层级,代码结构是一一对应的:
我们再看看 React Native 渲染到原生视图后的嵌套层级(iOS 用 Debug View Hierarchay,Android 用 Layout Inspector):
可以发现,ios的card2原生的View还在,但是安卓的第二个卡片View不见了。
这个现象是因为,就会发现 React Native Android UI 布局前,会对只有布局属性的 ViewLAYOUT_ONLY_PROPS 源码进行过滤,这样可以减少 View 节点和嵌套,对碎片化的 Android 更加友好。
所以React组件映射到原生View时候,嵌套结构并不是一一对应的。那么在这个背景下如何进行优化呢?
1. 减少嵌套层级数量(React.Fragment)
Fragments允许你将子列表分组,无需向DOM添加额外的节点
1 | class Table extends React.Component { |
最终输出的结果,会组合成一个table
1 | <table> |
可见Fragments主要作用是减少嵌套的层级,可以使用在业务组件封装的场景。
2. 减少GPU过度绘制
业务开发时候,如果不慎在多个层级都使用了相同的style,比如如下例子:
1 | render() { |
在渲染的时候,iOS与安卓会触发不同的GPU渲染机制。虽然背景色结果都是白色,但是GPU 的优化是不一样的。我们用 iOS 的 Color Blended Layers 和 Android 的GPU过度绘制调试工具查看最后的渲染结果:
对于 iOS 来说,出现红色区域,就说明出现了颜色混合:
Card1 的几个 View 都设置了非透明背景色,GPU 获取到顶层的颜色后,就不再计算下层的颜色了
Card2 的 Text View 背景色是透明的,所以 GPU 还要获取下一层的颜色进行混合
对于 Android 来说,GPU 会多此一举地渲染对用户不可见的像素。有一个颜色指示条:白 -> 蓝 -> 绿 -> 粉 -> 红,颜色越往后表示过度绘制越严重。
Card1 的几个 View 都设置了非透明背景色,红色表示起码发生了 4 次过度绘制
Card2 只有文字发生了过度绘制
由于过度绘制在iOS和安卓判定机制不一样,做视图优化时候,可以优先考虑优化安卓。可以从以下考虑优化方案:
减少背景色的重复设置:每个 View 都设置背景色的话,在 Android 上会造成非常严重的过度绘制;并且只有布局属性时,React Native 还会减少 Android 的布局嵌套
避免设置半透明颜色:半透明色区域 iOS Android 都会引起过度绘制
避免设置圆角:圆角部位 iOS Android 都会引起过度绘制
避免设置阴影:阴影区域 iOS Android 都会引起过度绘制
…
避免过度绘制的细节往往很多,建议长列表优化时候可以考虑。
长列表性能优化
预加载
预加载优化,主要分几个关键的阶段:
- 终端页面
- 引擎加载
- JS包加载
- JS执行
- 渲染页面
Bundle加载过程:
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 miaozixiong@gmail.com