编程这件事,基本绕不开性能优化。这篇文章就结合我实际踩过的坑和经验,把 React Native 里跟性能相关的知识梳理一下。
有关FlatList滚动白屏与渲染卡顿的优化,我放在了我的下一篇文章中单独来说ReactNative总结系列四-FlatList白屏卡顿优化
如何检查性能 - 使用react-devtools
优化之前,得先知道哪里慢。React Native 这边推荐用 React DevTools。
低版本 RN 还能用
react-dev-tools内置的react-tool,但高版本已经不再维护了,直接用react-devtools就好。注意版本要和项目里的react版本对应上。
基本用法
-
全局安装:
yarn global add react-devtools、npm install -g react-devtools- 临时用
npx react-devtools - 我的rn配置用最新版的会报错,使用6.1.2可以
json"react": "19.1.4", "react-native": "0.81.6", - 临时用
-
终端启动:
react-devtools
-
打开app,并打开dev menu(rn控制台里按'd'或者摇一摇或者手机里输入'comand+m'),会自动连接上
如果 DevTools 连不上,用
adb reverse 8097 8097把 8097 端口转发一下
-
这里点击App,点开就能看到 hooks、props、style 等 ,鼠标移上去还会在app里高亮显示
![]() |
![]() |
性能分析
- 切到 Profiler 面板,点击设置图标,打开录制造成渲染的原因

- 点开始录制
- 在 App 里做你要测的操作,完成后再点一下点结束录制

- 在 App 里做你要测的操作,完成后再点一下点结束录制
- 此时会生成火焰图,有颜色的部分表示发生了重新渲染(黄色一般说明渲染耗时较长)
3. 鼠标移动到对应的view上,能看到这次渲染是因为哪个 hook 变了、props 变了,还是别的什么原因 
- 最后需要根据你的预期和实际的渲染次数及view来判断是否合理,比如点了3次,应该刷新3次,却刷新了4次或者5次,或者渲染时,子组件并不需要渲染,但实际渲染了。那就要结合日志分析是哪里造成的多次渲染。
渲染相关的机制
这章介绍一下,React在渲染时的一些机制
require 和 import 在 RN 的行为
require:运行时加载,但只会加载一次 ,后续都是直接用运行环境里的缓存。- 那么如果你要require的库比较大,比如pinyin库,你放在顶层,就会导致一开始就去加载这个js,导致慢。那么你就可以不写在顶层,用require在实际需要使用时,再导入进来
import:分成静态import和动态import- 静态import:
import {Text} from 'react-native';放顶层 ,和require一样 - 动态import:
const rnModule =await import('react-native');代码运行到这时异步加载import里的内容 ,require是同步的
- 静态import:
JSX 里的匿名函数
写在 JSX 里的匿名函数,每次渲染都会重新创建,导致memo失效。处理方式:
- 用
useCallback包一层 - 或者把函数提到组件外部
使用Object.assign() 代替 ...
在 TS 或 core-js 里,大量使用 ... 展开运算符可能会有性能损失,使用 Object.assign() 来做对象合并。
异步更新与批量合并
- 在事件触发、useEffect时 多次调用setState,会合并执行,只触发一次渲染
- 在
render、setTimeout、Promise等 异步回调里多次 setState,调用几次setState就重新渲染多少次
这个特别在获取接口数据,返回后设置state时注意。
组件结构 > 记忆化 API
记住一个原则:优化组件结构,往往比用 memo、useMemo 这些"记忆" API 来得更高效、更重要。
- 组件层级越深,性能越差。很多优化的本质,其实都是在"减少层级、减少渲染影响的组件"
- 多使用自定义 Hook 把逻辑封装起来,既方便复用,也让代码看起来更加精简和整洁
Native组件props的更新机制
- 首先我们要知道一件事,就是
**js层重新渲染了,props变了,不一定会触发native层的props更新**- 如果你传的是
ReadableMap,RN 是会做深比较的
- 如果你传的是
比如js的props每次都生成一个新的obj:
{age: 18},如果这个age不变,只是obj的引用变了,那么native是不会收到更新的
- 低版本RN(老架构),从js层更新到native层时,是只要有一个变了,就把所有 props 重新设置一遍
- 高版本 RN(Fabric 架构)往原生传 props 时,已经能做到变了哪个字段才更新哪个。这一点在升级架构时可以多留意。
key 作用和用法
key 属性是给 React Diff 算法看的,用来在同层级下识别元素是新增、修改还是移除。
- key不变:组件继续存在,只根据新 props 重渲染。
- 变了 key:旧组件被卸载,新组件从头创建并挂载。开销大,但能方便地重置组件内部状态。
- 用 index 做 key 的大坑 :如果你是使用map来同时生成多个一样的组件时,rn会提示你让你都加上key。但是不要使用index来做key(除非数组不变变化)
- 当数组头部插入元素时,新插入的元素里的state会变成原来的第一个元素的state ,也叫内部状态错误。并且所有元素重渲染,最后一个元素还得重新生成组件重新挂载。
几个实际场景的表现:
jsx
{ids.map(id => (
// ❌ 不加 key,每次都会重新生成组件
<MemoItem id={id.toString()} />
))}
{ids.map(id => (
// ✅ 加上稳定 key,新增/删除项时其他项不会重渲染,只会进行移动
<MemoItem key={id} id={id.toString()} />
))}
...
const [items, setItems] = useState([
{ id: 'a', text: 'Apple' },
{ id: 'b', text: 'Banana' },
]);
// 删除第一个元素时...
const removeFirst = () => setItems(prev => prev.slice(1));
// ❌渲染(错误用法),删除后,banana会使用apple里的state不会变!!!
{items.map((item, index) => (
<Text key={index}>{item.text}</Text>
))}
...
// ⚠️ 但 key 管不了父组件
<MemoParent title={'我也会重新渲染'}>
<MemoItem key={'我有key'} id={'我有ID'} />
</MemoParent>
几个优化方案
使用memo缓存组件
- 具体查看Hook指南,这里不再赘述
拆分 Context,告别"连坐式渲染"
很多时候我们只是需要用 Context来更新数据,并不需要使用数据。但数据一变,这些组件也会被动重新渲染。
解决方案:使用多个context
- 一个 Context 只提供不变的状态更新函数
- 另一个 Context 提供可变的状态数据
**然后用不变的包裹住可变的,就能把更新视图的范围压到最小。**
这个思路我后来抽了个轻量的共享工具,查看小工具
善用 React 18 的新特性
- 使用
lazy+Suspense:延迟加载组件,加快首屏渲染速度。- suspense原理可以参考Suspense异步转同步原理
tsx
// 懒加载:只在需要时才加载该组件的代码
const HeavyComponent = lazy(() => import('./HeavyComponent'));
export default function App() {
const [showHeavy, setShowHeavy] = useState(false);
return (
<View style={{ flex: 1, justifyContent: 'center', padding: 20 }}>
<Text>首屏只渲染轻量内容</Text>
<Button title="加载重型组件" onPress={() => setShowHeavy(true)} />
{showHeavy && (
<Suspense fallback={<ActivityIndicator size="large" />}>
<HeavyComponent />
</Suspense>
)}
</View>
);
}
- 使用
useTransition和useDeferredValue:把不紧急的更新标记为过渡,自动推后优先保证用户交互的流畅感。
tsx
const [isPending, startTransition] = useTransition();
...
// 等空闲时才更新,优先保证界面。不过我目前用下来在rn里感受不是很大
startTransition(() => {
setFilter(text);
});
...
{isPending && <Text>正在更新...</Text>}
...
const [text, setText] = useState('');
const deferredText = useDeferredValue(text); // 延迟更新的值,界面上直接用这个延迟值
...
<Text>{deferredText}</Text>
...
可见时才加载组件
-
在长滚动页面里,对于那些非首屏可见的组件,可以用
IntersectionObserver做"可见性触发"- 一开始先放个占位组件,等
isIntersecting回调时再渲染真正的组件或者加载图片等
- 一开始先放个占位组件,等
-
既能减少首屏压力,又能降低运行时的渲染负担。

