🎨 React Native 性能优化指南——渲染篇
文章已于 2022-08-29 更新「图片」优化内容
文章已于 2022-01-22 更新「Fabric」架构章节
文章已于 2021-07-01 更新 Redux 优化内容
2020 年谈 React Native,在日新月异的前端圈,可能算比较另类了。文章动笔之前我也犹豫过,但是想到写技术文章又不是赶时髦,啥新潮写啥,所以还是动笔写了这篇 React Native 性能优化的文章。
本文谈到的 React Native 性能优化,还没到修改 React Native 源码那种地步,所以通用性很强,对大部分 RN 开发者来说都用得着。
本文的内容,一部分是 React/RN/Android/iOS 官方推荐的优化建议,一部分是啃源码发现的优化点,还有一部分是可以解决一些性能瓶颈的优秀的开源框架。**本文总结的内容你很少在网络上看到,所以看完后一定会有所收获。**如果觉得写的不错,请不要吝啬你的赞,把这篇 1w 多字的文章分享出去,让更多的人看到。
看文章前要明确一点,一些优化建议并不是对所有团队都适用的。有的团队把 React Native 当增强版网页使用,有的团队用 React Native 实现非核心功能,有的团队把 React Native 当核心架构,不同的定位需要不同的选型。对于这些场景,我在文中也会提一下,具体使用还需要各位开发者定夺。
目录:
- 一、减少 re-render
- 二、减轻渲染压力
- 三、图片优化那些事
- 四、对象创建调用分离
- 五、动画性能优化
- 六、长列表性能优化
- 七、React Native 性能优化用到的工具
- 八、推荐阅读
一、减少 re-render
因为 React Native 也是 React 生态系统的一份子,所以很多 React 的优化技巧可以用到这里,所以文章刚开始先从大家最熟悉的地方开始。
对于 React 来说,减少 re-render 可以说是收益最高的事情了。
1⃣️ shouldComponentUpdate
📄 文档: https://react.docschina.org/docs/optimizing-performance.html#shouldcomponentupdate-in-actionsx
简单式例:
class Button extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
if (this.props.color !== nextProps.color) {
return true;
}
return false;
}
render() {
return <button color={this.props.color} />;
}
}
无论哪篇文章,谈到 React 性能优化,shouldComponentUpdate
一定是座上宾。
我们通过这个 API,可以拿到前后状态的 state/props,然后手动检查状态是否发生了变更,再根据变更情况来决定组件是否需要重新渲染。
🔗 官方文档对 shouldComponentUpdate
的作用原理和使用场景已经说的非常清晰了,我就没有必要搬运文章了。在实际项目中,阅文集团的 🔗 React Native 应用**「元气阅读」**也做了很好的示范,🔗 Twitter 的性能优化分享也做的图文并茂,可有很高的参考价值,对此感兴趣的同学可以点击跳转查看。
在此我想提醒的是,shouldComponentUpdate 是强业务逻辑相关的,如果使用这个 API,你必须考虑和此组件相关的所有 props 和 state,如果有遗漏,就有可能出现数据和视图不统一的情况。所以使用的时候一定非常小心。
2⃣️ React.memo
📄 文档: https://react.docschina.org/docs/react-api.html#reactmemo
React.memo
是 React v16.6 中引入的新功能,是一个专门针对 React 函数组件的高阶组件。
默认情况下,它和 PureComponent
一样,都是进行浅比较,因为就是个高阶组件,在原有的组件上套一层就可以了:
const MemoButton = React.memo(function Button(props) {
return <button color={this.props.color} />;
});
如果想和 shouldComponentUpdate
一样,自定义比较过程,React.memo
还支持传入自定义比较 函数:
function Button(props) {
return <button color={this.props.color} />;
}
function areEqual(prevProps, nextProps) {
if (prevProps.color !== nextProps.color) {
return false;
}
return true;
}
export default React.memo(MyComponent, areEqual);
值得注意的是,areEqual()
这个函数的返回值和 shouldComponentUpdate
正好相反,如果 props 相等,areEqual()
返回的是 true
,shouldComponentUpdate
却返回的是 false
。
3⃣️ React.PureComponent
📄 文档: https://react.docschina.org/docs/react-api.html#reactpurecomponent
简单式例:
class PureComponentButton extends React.PureComponent {
render() {
return <button color={this.props.color} />;
}
}
和 shouldComponentUpdate
相对应,React 还有一个类似的组件 React.PureComponent
,在组件更新前对 props 和 state 做一次浅比较。所以涉及数据嵌套层级过多时,比如说你 props 传入了一个两层嵌套的 Object,这时候 shouldComponentUpdate
就很为难了:我到底是更新呢还是不更新呢?
考虑到上面的情况,我在项目中一般很少用 PureComponent
。虽然很简单易用,但是面对复杂逻辑时,反而不如利用 shouldComponentUpdate
手动管理简单粗暴。当然这个只是个人的开发习惯,社区上也有其他的解决方案:
- 把组件细分为很小的子组件,然后统一用
PureComponent
进行渲染时机的管理 - 使用 immutable 对象,再配合
PureComponent
进行数据比较(🔗 参考链接:有赞 React 优化) - ......
在这个问题上仁者见仁智者见智,在不影响功能的前提下,主要是看团队选型,只要提前约定好,其实在日常开发中工作量都是差不多的(毕竟不是每个页面都有必要进行性能优化)。
4⃣️ React.useMemo 和 React.useCallback
📄 文档: https://zh-hans.reactjs.org/docs/hooks-reference.html#usememo
React16 发布后,React 团队主推函数式组件和 hooks,其中官方提供的 useMemo
和 useCallback
都可以细粒地进行性能优化。具体的使用可以直接看官方文档。
5⃣️ 有技巧的使用 redux
假设一个 RN 项目使用 redux 管理全局状态,当从 0 开始开发一个新的页面时,因为前期项目比较简单逻辑比较少,我们往往会在整个 Page 上包一层 react-redux
,然后把 redux 的数据传进去,这样做在前期是没有问题的。
但是随着项目的迭代,这个页面的逻辑一定会越来越复杂,引用的组件和依赖的 redux
状态也会越来越多。这样就会导致一个问题:随便一个依赖的 redux
子状态改动,都会引起整个 Page 页面的 re-render
,即使依赖这个状态的组件只占整个页面的一小部分。
这种情况其实很好优化,那就是不要用 react-redux
包装整个 Page 页面,而是用 react-redux
包装直接依赖 redux 数据的组件,这样就能把组件重绘带来的性能影响控制到最小。