文章开始之前希望大家支持一下我独立开发的微信小程序“头脑旋风”,或微信扫描我的头像进入,谢谢支持~
React.memo
或 PureComponent
来避免组件不必要的重新渲染。shouldComponentUpdate
或 React.useCallback
优化函数组件的性能。keyExtractor
提供唯一键值。initialNumToRender
、maxToRenderPerBatch
和 windowSize
控制渲染的批次和范围。getItemLayout
提供固定高度以提高滚动性能。render
方法中直接定义函数和对象,将它们提取为组件外的常量或使用 useMemo
、useCallback
缓存。react-native-worker
或 Web Worker
。re-renders
:
Context
或 Redux
管理全局状态,但避免不必要的深度嵌套,结合 useSelector
或 useContext
的浅比较。JPEG
、WebP
),避免加载超高分辨率图片。react-native-fast-image
实现图片缓存和加载优化。GZIP
压缩和 HTTP
缓存。Realm
或 SQLite
)缓存频繁请求的数据。react-native-screens
的懒加载功能)。Hermes
引擎:
Hermes
是 Facebook
为 React Native
应用设计的 JavaScript
引擎,可减少应用启动时间并优化内存使用。Dev Tools
:
debug
、yellow box
和其他开发工具。Animated
中的 useNativeDriver: true
,将动画计算移至原生线程。react-native-reanimated
或 lottie-react-native
,能更高效地处理复杂动画。isMounted
检查。FlatList
或 SectionList
,避免使用 ScrollView
加载所有数据。React DevTools
:分析组件渲染问题。Flipper
:用于检查网络请求、日志和性能。Android Studio
和 Xcode Profiler
:分析原生性能瓶颈。在 React
或 React Native
中,减少组件的重新渲染是优化性能的关键。以下是一些减少不必要重新渲染的方法:
React.memo
React.memo
可以防止函数组件在相同的 props
下重新渲染。const MyComponent = React.memo(({ prop1 }) => {
console.log("Rendering");
return <Text>{prop1}</Text>;
});
如果 prop1
未改变,组件不会重新渲染。const Parent = () => {
return <Child onClick={() => console.log("Clicked")} />;
};
const handleClick = useCallback(() => {
console.log("Clicked");
}, []);
const Parent = () => {
return <Child onClick={handleClick} />;
};
useCallback
确保函数引用在依赖不变时保持稳定。useMemo
缓存计算值useMemo
缓存,避免每次渲染都重新计算。const Parent = ({ items }) => {
const processedItems = useMemo(() => {
return items.map(item => item * 2);
}, [items]);
return <Child items={processedItems} />;
};
shouldComponentUpdate
或 React.PureComponent
shouldComponentUpdate
或 React.PureComponent
控制是否需要重新渲染。class MyComponent extends React.PureComponent {
render() {
console.log("Rendering");
return <Text>{this.props.value}</Text>;
}
}
key
提高列表性能key
唯一且稳定,避免 React
重新创建 DOM
节点。 <FlatList
data={data}
renderItem={({ item }) => <Item key={item.id} {...item} />}
keyExtractor={(item) => item.id.toString()}
/>
useEffect
执行useEffect
中的逻辑如果没有依赖变化,可以跳过执行。
useEffect(() => {
console.log("Effect executed");
});
useEffect(() => {
console.log("Effect executed");
}, []); // 添加依赖项数组
Context
,避免一个状态变化影响整个组件树。
const Parent = () => {
const [count, setCount] = useState(0);
return (
<>
<ChildA count={count} />
<ChildB />
</>
);
};
const CountContext = React.createContext();
const Parent = () => {
const [count, setCount] = useState(0);
return (
<CountContext.Provider value={count}>
<ChildA />
<ChildB />
</CountContext.Provider>
);
};
Redux
或 Zustand
等状态管理工具,避免通过 props
层层传递。React DevTools
:检查组件渲染的频率。Profiler API
:分析组件渲染时间。import { Profiler } from "react";
const Parent = () => (
<Profiler id="Parent" onRender={(id, phase, actualTime) => {
console.log({ id, phase, actualTime });
}}>
<Child />
</Profiler>
);
在 React
中,React.memo
和 useCallback
是优化组件性能的常用工具。它们可以帮助减少不必要的重新渲染,从而提高应用的效率。
React.memo
的用法React.memo
是一个高阶组件,用于优化函数组件。它会对组件的 props
进行浅比较,如果 props
没有变化,就跳过重新渲染。
import React from "react";
const MyComponent = React.memo(({ value }) => {
console.log("Rendered!");
return <div>{value}</div>;
});
const Parent = () => {
const [count, setCount] = React.useState(0);
return (
<>
<MyComponent value="Hello" />
<button onClick={() => setCount(count + 1)}>Increment</button>
</>
);
};
Parent
组件重新渲染,MyComponent
也不会重新渲染,因为它的 props
没有变化。React.memo
默认使用浅比较,但你可以通过传递自定义比较函数来处理复杂 props
。const MyComponent = React.memo(
({ value }) => {
console.log("Rendered!");
return <div>{value.text}</div>;
},
(prevProps, nextProps) => {
// 自定义比较:如果 `value.text` 没变,不重新渲染
return prevProps.value.text === nextProps.value.text;
}
);
useCallback
的用法useCallback
是一个 React Hook
,用于缓存函数的引用。它会在依赖未改变时返回相同的函数实例。
useCallback
React
中,每次渲染都会重新创建函数实例。通过 useCallback
可以避免不必要的函数重新创建,从而减少传递给子组件的 props
变化。import React, { useState, useCallback } from "react";
const Child = React.memo(({ onClick }) => {
console.log("Child Rendered!");
return <button onClick={onClick}>Click Me</button>;
});
const Parent = () => {
const [count, setCount] = useState(0);
// 如果不使用 useCallback,每次渲染都会生成新的函数实例
const handleClick = useCallback(() => {
console.log("Button clicked");
}, []); // 依赖为空,函数实例只会创建一次
return (
<>
<p>Count: {count}</p>
<Child onClick={handleClick} />
<button onClick={() => setCount(count + 1)}>Increment</button>
</>
);
};
Parent
重新渲染时,由于 useCallback
,Child
组件不会重新渲染,因为 onClick
函数引用没有变化。useCallback
和 React.memo
配合useCallback
常与 React.memo
一起使用,确保传递给子组件的回调函数引用不变。const Child = React.memo(({ onClick }) => {
console.log("Child Rendered!");
return <button onClick={onClick}>Click Me</button>;
});
const Parent = () => {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log("Button clicked");
}, []);
return (
<>
<Child onClick={handleClick} />
<button onClick={() => setCount(count + 1)}>Increment</button>
</>
);
};
useCallback
的依赖管理const Parent = () => {
const [count, setCount] = useState(0);
// 如果依赖项未设置,当 count 变化时,函数不会更新
const handleClick = useCallback(() => {
console.log(count);
}, [count]); // 添加 count 作为依赖
return <button onClick={handleClick}>Log Count</button>;
};
React.memo
和 useCallback
,它们也有开销,适用于有明显性能问题的场景。React.memo
只进行浅比较,传递复杂对象时可能需要自定义比较函数。useCallback
和 useMemo
中正确设置依赖,避免错误或不必要的更新。优化 React Native
应用的启动时间是提升用户体验的关键因素,尤其在移动设备上,用户对应用响应速度非常敏感。以下是优化启动时间的原因及具体方法。
3 秒
的启动时间会显著增加用户放弃应用的概率。React Native
的启动时间?
启用 Hermes
引擎
Hermes
是 Facebook
为 React Native
开发的 JavaScript
引擎,能显著减少应用启动时间,尤其是 Android
。
React Native
项目中启用 Hermes
npx react-native init MyApp
android/app/build.gradle
文件:project.ext.react = [
enableHermes: true // 启用 Hermes
]
cd android && ./gradlew clean && cd ..
npx react-native run-android
JavaScript
初始化时间。减少 JavaScript
包大小
JavaScript
拆分为多个小文件,并按需加载功能。import()
方法按需加载模块。babel-plugin-import
按需引入模块。Metro Bundler
自带的压缩功能减少 JavaScript
包大小。npx react-native run-android --variant=release
延迟加载资源
const LazyComponent = React.lazy(() => import('./LazyComponent'));
const App = () => (
<React.Suspense fallback={<Text>Loading...</Text>}>
<LazyComponent />
</React.Suspense>
);
优化原生模块加载
react-native-clean-project
清理项目中的多余依赖。减少主线程阻塞
InteractionManager.runAfterInteractions
延迟非关键任务:InteractionManager.runAfterInteractions(() => {
// 非关键任务
});
使用原生启动屏幕
React Native
应用在启动时,可能需要加载 JavaScript
和原生模块。通过添加启动屏幕,可以在后台加载资源时向用户展示品牌形象。
操作步骤
react-native-splash-screen
。减少依赖项
移除不必要的依赖,尤其是会增加启动时间的庞大库。例如:
moment.js
(大体积)为 dayjs
。监控启动性能
使用性能监控工具检测启动时间的瓶颈:
React Native Performance Monitor
:在开发环境中启用性能监控。
Flipper
:检查网络请求、内存占用、组件加载时间。
Android Studio Profiler / Xcode Instruments
:分析原生层面的性能问题。
React Native
的 bridge
,它会影响性能吗?什么是 React Native
的 Bridge
?
在 React Native
中,Bridge
是一个关键概念,用于连接 JavaScript
代码(运行在 JavaScript
引擎中,如 Hermes
或 JSC
)和原生代码(运行在 Android
和 iOS
平台上)。
JavaScript 线程
:运行 React 和应用逻辑(UI 渲染逻辑、状态管理等)。Bridge
:一个通信层,用于在 JavaScript 线程和原生线程之间传递数据。原生线程
:负责原生视图的渲染、动画、手势处理等。Bridge
的作用:
JavaScript
代码调用原生模块(如摄像头、地理定位)。Bridge
把数据传回 JavaScript
。通信过程:
JavaScript
调用原生模块(通过 Bridge
)。JSON
格式。Bridge
发送回 JavaScript
。Bridge
对性能的影响
由于 React Native
的 Bridge
是异步的消息通道,其性能可能受到以下因素的影响:
JavaScript
和原生层的通信,都会有一定的延迟。UI
、滚动动画),Bridge
的性能瓶颈会导致卡顿。JavaScript
对象序列化为 JSON
格式,原生端需要将其反序列化。Bridge
,可能因为消息队列拥堵而导致掉帧。JavaScript
处理,Bridge
可能成为性能瓶颈。React Native
中,JavaScript
和原生线程独立运行。虽然这是为了避免阻塞,但如果线程之间的消息同步不及时,可能出现性能问题。如何优化 Bridge 性能?
JavaScript
。FlatList
或 SectionList
)。Bridge
。Bridge
调用完成。React Native
的原生动画驱动(如 Animated
和 LayoutAnimation
),而不是依赖 JavaScript
驱动的动画。JSI
(JavaScript Interface
)React Native
的新架构(Fabric
和 Turbo Modules
)通过 JSI
替代了传统的 Bridge
。JSI
提供了更高效的同步通信机制,避免了数据序列化和异步队列的开销。JSON
序列化。JSI
,需要确保你的 React Native
版本支持 Fabric
和 Turbo Modules
。Flipper
)监控 Bridge
的消息队列,分析通信延迟和数据传输量。总结
优点
:异步机制让 JavaScript
和原生线程解耦,降低线程阻塞风险。缺点
:通信延迟、数据序列化等开销可能影响性能,尤其是在高频通信场景下。Bridge
。JSI
的新架构,进一步提升通信效率。减少 JavaScript
和原生模块之间的通信开销是优化 React Native
应用性能的关键。以下是一些有效的策略和实践方法:
问题
:频繁的单次通信会增加 Bridge
的开销。解决方案
:将多次调用合并为一次批量调用。例如,合并状态更新或事件发送。 // 示例:批量发送数据
const sendData = (dataList) => {
NativeModules.MyNativeModule.sendBatchData(dataList);
};
问题
:高频任务(如滚动、动画)如果通过 Bridge
处理,可能导致卡顿。解决方案
:将高频任务逻辑尽量放在原生代码中。Animated
和 LayoutAnimation
等可以利用原生线程处理动画的工具。FlatList
)处理滚动,而不是自定义滚动逻辑。传递必要的数据
问题
:传递大数据对象或冗余数据会增加序列化和反序列化的时间。解决方案
:只传递关键数据,避免整个对象通过 Bridge。// 示例:仅传递必要字段
const sendUserData = (user) => {
NativeModules.MyNativeModule.sendUserData({
id: user.id,
name: user.name,
});
};
数据压缩
问题
:数据量过大可能导致性能瓶颈。解决方案
:在 JavaScript
层对数据进行压缩,再通过 Bridge
传递。例如,将 JSON 数据压缩成字符串:
import { gzip } from 'pako';
const compressedData = gzip(JSON.stringify(data));
NativeModules.MyNativeModule.sendData(compressedData);
JSI
(JavaScript Interface
)
问题
:传统 Bridge 依赖 JSON 序列化和反序列化。解决方案
:在支持新架构(Fabric 和 Turbo Modules)的 React Native 中使用 JSI。问题
:过多的事件监听器可能增加通信负担。解决方案
:优化事件监听逻辑,绑定必要的事件。// 示例:限制监听范围
useEffect(() => {
const subscription = DeviceEventEmitter.addListener('eventName', handleEvent);
return () => subscription.remove();
}, []);
```
import { InteractionManager } from 'react-native';
InteractionManager.runAfterInteractions(() => {
// 执行非关键任务
});
问题
:重复传输同样的数据会浪费带宽。解决方案
:将需要频繁访问的数据缓存到原生端,避免重复通信。DeviceEventEmitter
),通过事件监听代替主动通信。 //原生模块发送事件
DeviceEventEmitter.emit('customEvent', { key: 'value' });
// JavaScript 中监听
DeviceEventEmitter.addListener('customEvent', (data) => {
console.log(data);
});
Bridge
开销
Flipper
和 React Native Debugger
监控 Bridge
的通信频率和数据量。在 React Native
中,代码分割和动态加载是优化性能、减少初始加载时间的关键技术。以下是实现代码分割和动态加载的详细指南:
代码分割
动态加载
减少初始加载时间
:只加载启动时所需的代码,提高应用启动速度。按需加载
:延迟加载不常用的模块,提高运行时性能。内存优化
:避免一次性加载大块代码,减少内存占用。使用 React.lazy
实现组件的动态加载
示例:懒加载组件
import React, { Suspense } from 'react';
// 动态加载组件
const LazyComponent = React.lazy(() => import('./LazyComponent'));
const App = () => (
<Suspense fallback={<Text>Loading...</Text>}>
<LazyComponent />
</Suspense>
);
export default App;
注意
:
React.lazy
需要配合 Suspense
使用,fallback
属性用于在组件加载时显示占位内容。Suspense
的支持有限,尤其是某些场景下的服务端渲染。按需加载模块
动态加载函数或模块,可以避免一次性加载所有代码。
示例:动态加载模块
const loadModule = async () => {
const module = await import('./SomeModule');
module.default(); // 使用动态加载的模块
};
const App = () => (
<Button title="Load Module" onPress={loadModule} />
);
export default App;
使用 React Navigation 的延迟加载屏幕
如果使用 React Navigation,可以延迟加载屏幕组件,避免加载所有屏幕的代码。
示例:延迟加载屏幕
import React, { Suspense } from 'react';
import { createStackNavigator } from '@react-navigation/stack';
const Stack = createStackNavigator();
const LazyScreen = React.lazy(() => import('./LazyScreen'));
const App = () => (
<Stack.Navigator>
<Stack.Screen
name="LazyScreen"
component={() => (
<Suspense fallback={<Text>Loading Screen...</Text>}>
<LazyScreen />
</Suspense>
)}
/>
</Stack.Navigator>
);
export default App;
使用 Metro Bundler 的分包功能
Metro 是 React Native 默认的打包工具,支持代码分包功能。
实现分包
配置分包
:创建一个自定义的 metro.config.js 文件。const path = require('path');
module.exports = {
resolver: {
// 指定额外的包作为单独的依赖分包
extraNodeModules: {
someDependency: path.resolve(__dirname, 'node_modules/some-dependency'),
},
},
transformer: {
// 启用分包功能
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: true,
inlineRequires: false,
},
}),
},
};
创建分包入口
:将某些模块配置为单独的分包。使用动态加载的图片和资源
在 React Native 中,大型图片资源也可以延迟加载。
示例:动态加载图片
const LazyImage = ({ uri }) => {
const [loaded, setLoaded] = React.useState(false);
return (
<View>
{!loaded && <ActivityIndicator />}
<Image
source={{ uri }}
onLoad={() => setLoaded(true)}
style={{ width: 100, height: 100 }}
/>
</View>
);
};
避免加载无关依赖
通过 Babel 插件(如 babel-plugin-import
)按需加载库的模块,减少无关代码的加载。
示例:按需加载 Ant Design Mobile
npm install babel-plugin-import --save-dev
配置 .babelrc
:
{
"plugins": [
["import", { "libraryName": "antd-mobile", "libraryDirectory": "es", "style": true }]
]
}
然后只加载需要的模块:
import { Button } from 'antd-mobile'; // 只加载 Button 组件
Flipper
监控代码加载情况,确保延迟加载部分按预期执行。class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <Text>Something went wrong!</Text>;
}
return this.props.children;
}
}
const App = () => (
<ErrorBoundary>
<Suspense fallback={<Text>Loading...</Text>}>
<LazyComponent />
</Suspense>
</ErrorBoundary>
);
fallback
)能提供良好的用户体验。