在本文中,我们将介绍一套全面的技巧和策略,旨在使开发人员能够优化他们的 React Native 应用程序,将它们提升到最佳性能,并提供无与伦比的用户体验。
提高 React Native 应用程序的性能对于提高用户体验至关重要。但是许多开发人员忽视了性能优化的重要性,他们通常优先考虑代码的功能,而不考虑其对速度和响应能力的影响。
杰出的开发人员之所以与众不同,是因为他们能够在编码时认真考虑性能影响。
平衡 JS 线程和主线程之间的动画
在任何应用程序中,动画都是比较耗费性能的,在React Native 中也是如此。
React Native 使用两个主要线程运行:用于执行 JavaScript 代码的 JS 线程和主要负责渲染用户界面和响应用户输入的主线程。
默认情况下,动画在主线程上运行。但是,主线程上的繁重动画工作负载可能会导致性能问题,例如丢帧。
让我们来看一个场景,你有一个 React Native 应用程序在屏幕上显示一个可拖动的元素(例如一个可拖动的球)。用户可以使用触摸手势在屏幕上拖动此元素,并且您的目标是使其移动平滑。
在上面的场景中,当您在屏幕上拖动球时,主线程将忙于收集用户触摸。如果我们在主线程上执行动画,它会进一步加重负担。结果可能是丢帧和性能问题。在这种情况下,我们可以使用下面讨论的解决方案在 JS 线程上执行动画任务。
解决方案 1:使用 useNativeDriver
useNativeDriver
是一个 React Native 动画属性,您可以在 React Native 应用程序中为元素制作动画时使用。
当用户将此属性的值设置为 true 时,React Native 应用程序将在主线程上渲染动画。但是,如果考虑主线程将忙于其他任务,则可以通过设置 useNativeDriver: false
将动画转移到 JS 线程。
例如:
kotlin
Animated.timing(this.state.animatedValue, {
toValue: 1,
duration: 500,
useNativeDriver: false, //
在上面的代码中,React Native 将检查该 useNativeDriver
值,并将动画转移到 JS 线程。
您可以在此处了解有关动画的更多信息。
解决方案 2:使用 InteractionManager
在某些情况下,JS 线程和主线程都会很忙。例如,应用程序可能正在获取 API 数据,执行一些逻辑,并在 UI 上呈现它。
在这种情况下,JS 线程忙于获取数据和执行逻辑,而主线程则忙于显示 UI。当两个线程都处于接合状态时,尝试运行动画可能会导致丢帧和性能问题。
在这种情况下, 可以使用InteractionManager
。首先启动动画。动画完成后,React Native 将调用 InteractionManager.runAfterInteractions
执行 JS 代码。然后,JS 代码将调用 API 并在 UI 上显示数据。
此方法有助于避免在同时执行 JS 代码和动画时使 JS 线程过载。
示例:
javascript
InteractionManager.runAfterInteractions(() => {
/* Code to run after Animation completed */
});
减少不必要的重新渲染
减少在 React Native 中进行不必要的重新渲染对于保持最佳性能至关重要。每当应用程序重新渲染时,JS 线程都会创建一个 JS 包文件并将其传递给 React Native 桥,然后将其移交给主线程。
应用程序重新渲染的次数越多,JS 线程和主线程之间发生的传递就越多,这可能会降低应用程序的性能。
解决方案 1:缓存组件
React.memo
是 React 提供的高阶组件,用于减少不必要的重新渲染来优化功能组件。
当你用 React.memo
包装一个功能组件时,React 会缓存该组件,这意味着它只会在组件的 prop 发生变化时重新渲染该组件。如果渲染之间的依赖保持不变,React 将重用之前渲染的结果,从而避免再次渲染组件的成本。
示例:
javascript
const MyComponent = React.memo((props) => {
// component logic
});
解决方案 2:使用 useCallback 函数
当父组件为其子组件设置回调函数时,每当父组件重新渲染时,也会重新创建回调函数,从而返回新的函数引用。
因此,子组件会将其视为回调函数值的变化,提示它重新渲染,即使使用了 React.memo
也是如此。因此,子组件确实会重新渲染。
为了缓解这种情况,可以使用 useCallback
以防止在每次重新渲染父组件时重新创建函数引用。
如果要使用具有新状态值的回调函数,则必须重新创建该函数。若要使用更新的状态值重新创建函数,可以使用 useCallback
中的依赖项部分。
示例:
ini
const memoizedCallback = useCallback(() => {
// callback logic
}, [dependency]);
解决方案 3:尽量避免使用 Redux 更新本地状态
使用 Redux 数据更新状态可能会导致组件呈现两次:第一次是在更新 Redux 数据时,第二次是在使用 Redux 更新本地状态时。
通过遵循这种方法,我们可以避免不必要的组件重新渲染。因此,尽量不要使用 Redux 更新的存储数据来更新本地状态。
相反,直接在 UI 中使用 Redux 存储值。如果在最初显示 Redux 数据之前需要计算,则仅使用具有 Redux 的组件状态。
解决方案 4:记住函数结果
用 useMemo
记住函数结果。这将执行函数并将值存储在内存中。
当应用再次调用该函数时,它会从缓存中检索数据,而不是重复执行整个函数。您可以添加依赖项以重新执行函数并存储新结果。
ini
const handleClick = useMemo(() => {
// handle click
}, [dependency]);
解决方案 5:避免内联函数
避免使用内联函数。相反,请将箭头函数与useCallback
useMemo
结合使用,以防止不必要的重新渲染。
当父组件重新渲染时,也会使用新引用重新创建函数。如果我们将任何函数作为属性传递给子组件,则子组件也将重新渲染。
为了解决这个问题,我们可以使用 useCallback
函数来防止函数重新创建。
此外,通过使用 useMemo
,我们可以避免重新渲染子组件。
此外,通过使用命名函数而不是内联函数,您的代码将更具有可读性和可维护性。
scss
<Text onPress={() => pressed()}>Press Me</Text> // Avoid this inline function.
<Text onPress={pressed}>Press Me</Text> // recommended.
优化图像
解决方案 1:使用 SVG 图标和图像
SVG 文件包含描述图像/图标路径和线条的 XML 代码。SVG 图像与分辨率无关,它们缩放到任何大小而不会失去清晰度。
由于 SVG 图像是使用矢量路径而不是像素数据呈现的,因此在应用程序中渲染时,它们通常消耗更少的内存。这可以提高图片加载性能,尤其是在内存资源有限的设备上。
SVG 文件的文件大小往往比 PNG 或 JPEG 等光栅图像格式小,尤其是对于简单或几何图形。这样可以缩短下载时间,并减少应用程序的整体内存占用。
解决方案 2:使用 WebP 图像获得无损图像质量
WebP 是 Google 开发的一种现代图像格式,可在不影响质量的情况下有效减小图像大小。WebP 图像尺寸较小,但质量较好,可在屏幕上更快地显示。
解决方案 3:缓存图像以加快渲染速度
利用缓存机制来存储以前加载的图像,从而减少从网络重复获取图像的需要。您可以使用 react-native-fast-image npm 包缓存 URL 图像。该库将立即在屏幕上显示缓存的图像。
使用稳定的 NPM 包
选择更小、更稳定的 npm 包。这些包不仅可以减小 React Native 应用程序的大小,还可以通过其稳定的代码库确保效率。
为了确定适合您功能的包,请考虑 npm 网站上的以下几点:
- 比较具有相同功能的不同类型的 npm 包。
- 检查 npm 包的每周下载量。每周下载次数较多的软件包更可取。
- 检查 npm 包大小。尺寸较小的包在项目中占用的空间更少,从而减小了项目大小。
- 检查发布次数。版本数量越多,表明开发人员的维护越好。
- 检查上次更新。这有助于确定包是否仍由某人维护。
遵循这些要点将有助于选择正确的 npm 包。
在组件中使用样式表
利用 StyleSheet 模块设置组件样式的优点是仅在初始调用期间创建对象。后续调用将重用已创建的样式对象引用。此方法有助于减少每次重新渲染时样式对象的创建。
使用 Flatlist 提高性能
使用 FlatList 渲染列表,而不是将 ScrollView 与 map 函数一起使用。FlatList 仅展示当前在屏幕上可见的项,这有助于通过仅展示必要的项来优化代码。
避免内存泄漏
内存泄漏是指程序不释放已分配但不再使用的内存 (RAM) 的情况。
如果不解决内存泄漏问题,可能会导致程序消耗越来越多的内存,最终导致程序速度变慢,甚至由于可用内存不足而崩溃。
解决方案 1:注销计时器/侦听器/订阅
当我们注册任何计时器、侦听器或订阅时,请务必在组件卸载前取消注册它们。否则,即使这些组件不再展示,这些计时器、侦听器或订阅也将继续调用事件,从而导致未使用的内存增加。
示例:
javascript
useEffect(() => {
// Registering event listener when component mounts
const handleAppStateChange = (nextAppState: AppStateStatus) => {
console.log("App state changed to:", nextAppState);
};
AppState.addEventListener("change", handleAppStateChange);
// Unregistering event listener when the component unmounts
return () => {
AppState.removeEventListener("change", handleAppStateChange);
};
}, []);
解决方案 2:避免不必要地使用全局变量
只要应用正在运行,全局变量就会持续存在,因为它们可以从应用范围内的任何位置访问。因此,垃圾回收器会将它们解释为仍在使用中,并且不会解除分配它们。
解决方案 3:循环对象引用
在两个对象相互指向的情况下创建对象引用将阻止垃圾回收,因为垃圾回收器会认为这些对象仍在使用中。
arduino
const person1 = {
name: "Alice",
friend: person2,
};
const person2 = {
name: "Bob",
friend: person1,
};
删除控制台日志
控制台日志可以通过将日志打印到控制台来降低应用性能。因此,建议避免在生产中使用控制台日志。
解决方案 1:使用 DEV 全局变量
在应用程序文件中添加以下代码,并在需要输出日志时使用它。此代码将仅在开发模式下_DEV_
运行console.log
,但避免在生产模式下运行 console.log
。
示例:
javascript
const logger ={
log: __DEV__ ? console.log : ()=>{},
error:__DEV__ ? console.error : ()=>{}
}
logger.log("show values" ,data);
在此代码片段中, logger.log
和 logger.error
分别使用 console.log
和 console.error
。仅当_DEV_
结果为 true 时 ,应用程序正在开发模式下运行。否则,它们将被设置为空函数,确保它们在生产模式下不起作用。
使用 babel-plugin-transform-remove-console
babel-plugin-transform-remove-console 插件将 console.log
在生产版本中删除。这样可以提高应用程序在生产模式下的性能。
结论
总之,优化 React Native 应用程序性能对于提升用户体验至关重要。通过平衡动画、最大限度地减少重新渲染、优化图像以及使用稳定的 npm 包,可以提高应用程序的速度和响应能力。
优先考虑这些优化技术可确保在当今竞争激烈的应用市场中实现最佳性能和卓越的用户满意度。