性能概览 · React Native 框架

性能概览 · React Native 框架

性能概览使用 React Native 而非基于 WebView 的工具的一个重要原因,是为了达到至少 60 帧每秒(FPS)的性能,并为你的应用提供原生般的外观和体验。只要可行,我们都会让 React Native 自动处理优化,让你专注于应用逻辑而无需担心性能问题。然而,在某些方面我们尚未达到理想水平;此外,在某些情况下,React Native(就像直接编写原生代码一样)也无法为你确定最佳的优化方案。在这些情况下,手动干预就变得非常有必要。我们致力于默认提供丝滑顺畅的 UI 性能,但在某些特殊情况下,这可能无法实现。

本指南旨在传授一些基础知识,帮助你排查性能问题,并讨论常见的性能问题来源及其建议解决方案。

你需要了解的有关帧(frames)的知识​

你祖辈那一代人称电影为“活动影像”(moving pictures)是有原因的:视频中逼真的运动是一种幻觉,通过以恒定的速度快速切换静态图像而产生。我们将这些图像中的每一张称为“帧”。每秒显示的帧数直接影响视频(或用户界面)看起来的流畅度和真实感。iOS 和 Android 设备至少以每秒 60 帧的速度显示画面,这意味着你和 UI 系统最多只有 16.67 毫秒的时间来完成所有工作,以生成用户在该时间间隔内看到的静态图像(帧)。如果你无法在分配的时间段内完成生成该帧所需的工作,你就会“掉帧”,UI 看起来就会变得无响应。

为了不让你感到困惑,请打开应用中的 开发菜单(Dev Menu)并切换 Show Perf Monitor(显示性能监视器)。你会注意到有两种不同的帧率。

JS 帧率(JavaScript 线程)​

对于大多数 React Native 应用,业务逻辑运行在 JavaScript 线程上。这里是 React 应用所在的地方,也是执行 API 调用、处理触摸事件等操作的地方。对原生支持视图的更新会在事件循环的每次迭代结束时(如果一切顺利,在帧截止时间之前)进行批处理,并发送到原生端。如果 JavaScript 线程在某一帧中无响应,则会被视为掉帧。例如,如果你在一个复杂应用的根组件上设置了新的状态,并导致计算量巨大的组件子树重新渲染,这可能会耗时 200 毫秒并导致丢失 12 帧。在此期间,任何由 JavaScript 控制的动画都会显得卡住。如果掉帧严重,用户会有明显的感受。

响应触摸就是一个例子:如果你在 JavaScript 线程上跨多个帧执行工作,你可能会注意到对 TouchableOpacity 的响应出现延迟。这是因为 JavaScript 线程正忙,无法处理从主线程发送过来的原始触摸事件。因此,TouchableOpacity 无法响应触摸事件并命令原生视图调整其透明度。

UI 帧率(主线程)​

你可能已经注意到,原生堆栈导航器(例如 React Navigation 提供的 @react-navigation/native-stack)的开箱即用性能优于基于 JavaScript 的堆栈导航器。这是因为转换动画是在原生的主 UI 线程上执行的,因此它们不会被 JavaScript 线程上的掉帧所中断。

同样,当 JavaScript 线程被锁定时,你仍然可以顺畅地滚动 ScrollView,因为 ScrollView 运行在主线程上。滚动事件会被分派到 JS 线程,但滚动过程的发生并不依赖于 JS 线程的接收。

常见的性能问题来源​

在开发模式下运行(dev=true)​

在开发模式下运行时,JavaScript 线程的性能会大幅下降。这是不可避免的:为了提供良好的警告和错误信息,运行时需要做更多的工作。请务必在 发布版本(release builds)中测试性能。

使用 console.log 语句​

在运行打包后的应用时,这些语句可能会在 JavaScript 线程中造成巨大的瓶颈。这也包括来自调试库(如 redux-logger)的调用,因此在打包前请务必移除它们。你也可以使用这个 babel 插件 来移除所有的 console.* 调用。你需要先使用 npm i babel-plugin-transform-remove-console --save-dev 安装它,然后按如下方式编辑项目目录下的 .babelrc 文件:

JSON{ "env": { "production": { "plugins": ["transform-remove-console"] } }}

这将自动移除项目中发布(生产)版本里的所有 console.* 调用。

即使你的项目中没有编写任何 console.* 调用,也建议使用此插件,因为第三方库中也可能存在这些调用。

FlatList 渲染太慢,或大型列表的滚动性能较差​

如果你的 FlatList 渲染速度较慢,请确保已经实现了 getItemLayout,通过跳过渲染项的测量来优化渲染速度。

此外还有其他针对性能优化过的第三方列表库,包括 FlashList 和 Legend List。

因在 JavaScript 线程上同时执行大量工作而导致 JS 线程掉帧​

“导航器转换缓慢”是这种情况最常见的表现,但也有其他情况可能发生。使用 InteractionManager 是一个不错的方法;但如果延迟执行工作带来的用户体验代价过高,你可能需要考虑 LayoutAnimation。

Animated API 目前在 JavaScript 线程上按需计算每一帧,除非你设置了 useNativeDriver: true;而 LayoutAnimation 利用了 Core Animation,不受 JS 线程和主线程掉帧的影响。

使用此功能的一个场景是:在动画弹出模态框(从顶部滑入并淡入半透明遮罩)的同时,初始化并接收多个网络请求,渲染模态框内容,并更新打开该模态框的视图。有关如何使用 LayoutAnimation 的更多信息,请参阅动画指南。

注意事项

LayoutAnimation 仅适用于“即发即忘”的动画(“静态”动画)——如果动画必须可中断,则需要使用 Animated。

在屏幕上移动视图(滚动、平移、旋转)导致 UI 线程掉帧​

这在 Android 上尤为明显,特别是在透明背景的文本位于图片上方,或者任何需要 alpha 合成来在每一帧重绘视图的情况下。你会发现启用 renderToHardwareTextureAndroid 对此有显著帮助。对于 iOS,shouldRasterizeIOS 默认已启用。

注意不要过度使用此属性,否则内存使用量可能会飙升。在使用这些属性时,请分析你的性能和内存使用情况。如果你不再计划移动该视图,请关闭此属性。

对图像大小进行动画处理导致 UI 线程掉帧​

在 iOS 上,每次调整 Image 组件 的宽度或高度时,它都会从原始图像重新裁剪和缩放。这开销极大,特别是对于大图而言。建议改用 transform: [{scale}] 样式属性来对大小进行动画处理。例如,当你点击一张图片并将其放大到全屏时,就可以使用这种方式。

我的 TouchableX 视图响应不够灵敏​

有时,如果我们调整正在响应触摸的组件的透明度或高亮效果,同时又在该帧内执行了某个动作,那么在 onPress 函数返回之前,我们可能看不到那个效果。如果 onPress 设置的状态导致了繁重的重新渲染并导致掉帧,就可能出现这种情况。解决此问题的方法是将 onPress 处理程序中的所有动作包裹在 requestAnimationFrame 中。

React TSXfunction handleOnPress() { requestAnimationFrame(() => { this.doExpensiveAction(); });}

相关创作

明日之后服务器如何删除
365bet足球网址

明日之后服务器如何删除

10-28 👁 3135
青春电视剧大全
365bet足球网址

青春电视剧大全

08-14 👁 5739