@[toc]
React Native 做跨平台开发已经很成熟了,但只要在项目里遇到高频通信、大体积 JSON、实时 UI 更新这些需求时,Bridge 卡顿几乎是必踩的坑。尤其像大表单提交、实时绘图、高频硬件数据采集等场景,很容易出现 JS 卡住、页面掉帧或者操作延迟。
这篇文章就来从原理到实战把这个问题讲清楚,并给出一套可直接落地的优化方案,顺带附可运行的 Demo 代码。
Bridge 到底为什么卡?
先把原理讲透,你就知道为什么你的接口调用越来越慢、UI 更新越来越卡。
1. RN 核心通信机制:Bridge 是单通道串行模型
React Native 0.69 之前主要依赖 Bridge 通信。JS 和 Native 是两个不同线程,所有通信都走一个 JSON 序列化的通道。
这个通道有两个天生问题:
- 单线程排队:每个调用要经过序列化/反序列化,通信是串行的。
- 大 JSON 会拖慢整个队列:因为通道只有一个,一旦你传一个 1MB JSON,队列里所有任务都得等它处理完。
就跟高速路上有个大货车卡着一样,后面的小车全部跟着堵。
2. 高频通信直接压垮通道
比如实时绘图、陀螺仪数据 60 次/s、蓝牙通知 30 次/s,每次都走 Bridge,JS 线程很快就炸了。
3. UI 更新依赖 JS 线程
别忘了 RN UI 的 Diff + 更新逻辑都在 JS Thread 上。
如果 JS Thread 被:
- 大 JSON 解析
- 高频事件塞满
- Promise 频繁创建与调度
那 UI 就一定卡。
JSI / TurboModule:为什么更快?
JSI(JavaScript Interface)不再使用 JSON 序列化,而是允许 JS 直接访问 Native C++ 对象。
优势非常明显:
- 没有 JSON 序列化,直接访问 Native 函数。
- 多线程支持更灵活。
- 极大降低通信开销,尤其适合高频、小数据量调用。
TurboModule 则是基于 JSI 的模块系统,实现更快的 JS <-> Native 绑定。
降低通信频次:你必须做的第一件事
不管有没有用 JSI/TurboModule,你都不能让高频数据一条条往 Bridge 里丢。
最佳实践:
1. 数据批量化(Batching)
每 100ms 发送一次,而不是每次事件都发。
2. 事件合并(Throttling / Debouncing)
例如表单输入:
- 用户输入 20 次
- 你只同步 2 次就够了(节流)
3. 让 Native 做更多事
比方说实时绘图,不要每帧通知 JS,让 Native 直接在画布上画。
数据压缩、序列化优化
传大 JSON 是卡顿最常见原因。
你可以:
- 用 protobuf / flatbuffers 减少序列化体积
- 用 binary 数据代替 JSON
- 字段瘦身------只传必要字段,而不是上百个字段塞在一起
实战案例 1:大表单提交卡顿优化
场景:一个表单有 100+ 输入项,每次输入需要保存草稿。
初始做法(错误示范):
js
onChangeText={(value) => {
NativeModules.FormModule.saveDraft({ field: 'name', value })
}}
每输入一个字符就走一次 Bridge。
用户输入快一点,Bridge 立马堵到爆。
优化方案:节流 + 批量化更新
js
import _ from 'lodash';
const saveDraftBatch = _.throttle((data) => {
NativeModules.FormModule.saveDraftBatch(data);
}, 300);
onChangeText={(value) => {
pendingDraft['name'] = value;
saveDraftBatch(pendingDraft);
}}
优势:
- 即使输入 20 次,也只同步一次。
- Native 一次性处理多个字段。
实战案例 2:JSI 实时计数器(可运行 Demo)
下面我们写一个完整的 JSI Module Demo,展示如何使用 JSI 实现高性能通信。
1. 创建 Native 模块(Android 示例)
CounterModule.cpp
cpp
#include <jsi/jsi.h>
#include <mutex>
using namespace facebook;
int counter = 0;
std::mutex counterMutex;
jsi::Value increment(jsi::Runtime &rt, const jsi::Value *, size_t) {
std::lock_guard<std::mutex> lock(counterMutex);
counter++;
return jsi::Value(counter);
}
void installCounter(jsi::Runtime &rt) {
auto incrementFunc = jsi::Function::createFromHostFunction(
rt,
jsi::PropNameID::forAscii(rt, "increment"),
0,
increment
);
rt.global().setProperty(rt, "Counter", incrementFunc);
}
2. 注册 JSI 模块
cpp
void install(jsi::Runtime &rt) {
installCounter(rt);
}
3. JS 侧直接调用(不经过 Bridge!)
js
setInterval(() => {
const result = global.Counter();
console.log('counter = ', result);
}, 16);
每 16ms 调用一次,相当于每秒 60 次,JSI 完全不压力。
如果换成 Bridge,肯定会卡。
UI 更新为什么跟不上?如何解决?
RN UI 更新依赖 JS 线程,常见卡顿原因:
- JS 在处理 Promise 队列
- JSON 解析大对象
- 高频 setState 触发 Re-render
解决方案:
1. 使用 useMemo / useCallback / React.memo 避免无意义重渲染
js
const ExpensiveView = React.memo(({ value }) => {
return <Text>{value}</Text>
});
2. 合并 UI 更新
js
setState(prev => ({
...prev,
...newValues
}));
3. Native 驱动动画(Reanimated / LayoutAnimation)
让 UI 在 Native 执行,不走 JS。
可运行 Demo:TurboModule 极速调用
示例演示如何用 TurboModule 在 JS 中调用 Native 方法。
TypeScript 声明
ts
import { TurboModule, TurboModuleRegistry } from 'react-native';
export interface Spec extends TurboModule {
multiply(a: number, b: number): number;
}
export default TurboModuleRegistry.getEnforcing<Spec>('MathModule');
Native(iOS Swift)
swift
@objc(MathModule)
class MathModule: NSObject, TurboModule {
func multiply(_ a: Double, _ b: Double) -> Double {
return a * b
}
}
JS 使用
js
import MathModule from './MathModule';
const result = MathModule.multiply(2, 3);
console.log('result = ', result);
执行速度远快于传统 Bridge。
总结
RN 与原生通信卡顿,核心原因就是:
- Bridge 是单通道串行模型,容易被大 JSON 和高频事件堵住
- JS 线程承载 UI 更新,压力更大
- 高频通信本身就得优化,不管是否使用 JSI
最有效的优化方法:
- 降低通信频次(批量、节流、合并事件)
- 数据压缩、减少 JSON 体积
- 让 Native 承担更多计算
- 用 JSI / TurboModule 替换 Bridge,实现低延迟通信
- UI 更新避免不必要的 re-render,让动画走 Native