一、概览
使用 React Native 替代基于 WebView 的框架开发 App 的主要理由之一是实现每秒 60 帧的流畅体验,并获得类似原生 App 的外观和手感。React Native 团队致力于优化性能,使开发者能专注于业务逻辑,但在某些情况下仍需人工干预。
1.1、JS 帧率(JavaScript 线程)
大多数 React Native 应用的业务逻辑运行在 JavaScript 线程上,这是处理 React 组件、API 调用和触摸事件的线程。数据更新到原生视图是批量进行的,在事件循环中发送。如果 JavaScript 线程未能及时响应一帧,就会发生丢帧。例如,在复杂应用中调用 this.setState 可能导致昂贵的重绘,耗时 200 毫秒(12 帧丢失),从而使 JavaScript 控制的动画卡顿。超过 100 毫秒的卡顿用户就能明显感知。
1.2、UI 帧率(主线程)
当 JavaScript 线程卡顿时,ScrollView 等运行在主线程上的组件仍能流畅滚动,因为滚动事件分发到 JS 线程但不必要立即响应。
二、常见卡顿原因
卡顿可能由过度绘制、JS 执行耗时、动画问题或内存问题引起。以下是一些常见原因及解决思路。
2.1、开发模式 (dev=true)
开发模式下 JavaScript 线程性能较差,因为需要运行警告、错误检查和属性验证等。务必在发布模式下测试性能。本地调试时,可通过设置开发菜单中的 dev=false 并重启来模拟发布模式效果。
2.2、console.log 语句
在离线包应用中,大量控制台打印可能拖累 JavaScript 线程。注意第三方库如 redux-logger 也可能包含打印语句,发布前需确保移除。可以使用 Babel 插件 transform-remove-console 自动移除生产环境中的 console 调用。配置如下:
{
"env": {
"production": {
"plugins": ["transform-remove-console"]
}
}
}
2.3、使用动画改变图片尺寸时,UI 线程掉帧
在 iOS 上,调整 Image 组件的宽度或高度需要重新裁剪和缩放原始图片,开销很大。建议使用 transform: [{scale}] 样式属性来改变尺寸,例如放大图片到全屏时。
2.4、重绘几乎无变化的页面时,JS 帧率严重降低
实现 shouldComponentUpdate 函数来指定重绘条件。对于纯组件(界面完全由 props 和 state 决定),可以使用 PureComponent。不可变数据结构有助于快速比较和重绘。
2.5、Touchable 系列组件响应不佳
如果 onPress 函数中的操作与点击效果发生在同一帧,可能导致效果延迟。解决方案是将操作封装到 requestAnimationFrame 中:
handleOnPress() {
requestAnimationFrame(() => {
this.doExpensiveAction();
});
}
三、React Native 性能优化列举
- 首屏渲染问题:采用 JS Bundle 拆包,将基础框架和业务模块分开,动态加载以加快启动速度。
- 图片问题:避免从 JavaScript 包加载大量图片,优先使用原生端资源或压缩图片,推荐 WebP 或 JPG 格式。
- 缓存:对频繁使用的数据(如日期选择器数据源)进行缓存或本地存储,减少重复计算。
- 延迟加载:优先执行与页面展示相关的代码,延迟非关键操作如埋点、GIF 动画。对长列表中的图片使用懒加载。
- 动画:简单动画使用
LayoutAnimation,复杂动画使用Animated并设置useNativeDriver: true以提高性能。 - 响应速度:在 JavaScript 线程繁忙时,使用
InteractionManager、requestAnimationFrame或setTimeout(0)延迟任务执行。 - 刷新问题:通过
PureComponent或自定义shouldComponentUpdate避免不必要的渲染。注意权衡使用成本。 - 预加载:对可能用到的内容预先加载,如图片浏览器中的相邻图片。
- FlatList 优化:
- 使用
getItemLayout避免动态计算高度。 - 调整
windowSize控制缓存数量,平衡内存和性能。 - 设置
maxToRenderPerBatch确保流畅滚动。 - 在 Android 上启用
removeClippedSubviews优化内存。
- 使用
- 使用 Fragment:减少不必要的视图层级,使用
React.Fragment或<>替代额外 View 包装。 - Redux 相关优化:
- 在 reducer 中避免无意义的数据变动,直接返回原 state 如果未改变。
- 对复杂数据类型,未改变时使用原对象。
- 自定义
areStatesEqual、areStatePropsEqual等对比方法,使用深度比较减少重渲染。
四、自己项目做过的优化
- 优化逻辑减少 action:分析卡顿时的 action 行为,清除无用操作,简化步骤。
- 进入频道的逻辑变更:删除重复的通知,减少逻辑冗余。
- 网络加载逻辑:延迟加载非关键界面和数据,如话题和文件数据,注意依赖关系。
- 自定义 shouldComponentUpdate:在根组件和复杂组件中重写该方法,结合 Redux connect 的选项函数控制渲染。
- 代码优化:
- 避免在
mapStateToProps中生成新对象,防止过度绘制。 - 返回基本数据类型以减少对比耗时。
- 避免在