RN 与原生通信时出现性能瓶颈(Bridge 卡顿)怎么办?

@toc

React Native 做跨平台开发已经很成熟了,但只要在项目里遇到高频通信、大体积 JSON、实时 UI 更新这些需求时,Bridge 卡顿几乎是必踩的坑。尤其像大表单提交、实时绘图、高频硬件数据采集等场景,很容易出现 JS 卡住、页面掉帧或者操作延迟。

这篇文章就来从原理到实战把这个问题讲清楚,并给出一套可直接落地的优化方案,顺带附可运行的 Demo 代码。

Bridge 到底为什么卡?

先把原理讲透,你就知道为什么你的接口调用越来越慢、UI 更新越来越卡。

1. RN 核心通信机制:Bridge 是单通道串行模型

React Native 0.69 之前主要依赖 Bridge 通信。JS 和 Native 是两个不同线程,所有通信都走一个 JSON 序列化的通道。

这个通道有两个天生问题:

  1. 单线程排队:每个调用要经过序列化/反序列化,通信是串行的。
  2. 大 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++ 对象。

优势非常明显:

  1. 没有 JSON 序列化,直接访问 Native 函数。
  2. 多线程支持更灵活
  3. 极大降低通信开销,尤其适合高频、小数据量调用。

TurboModule 则是基于 JSI 的模块系统,实现更快的 JS <-> Native 绑定。

降低通信频次:你必须做的第一件事

不管有没有用 JSI/TurboModule,你都不能让高频数据一条条往 Bridge 里丢。

最佳实践:

1. 数据批量化(Batching)

每 100ms 发送一次,而不是每次事件都发。

2. 事件合并(Throttling / Debouncing)

例如表单输入:

  • 用户输入 20 次
  • 你只同步 2 次就够了(节流)

3. 让 Native 做更多事

比方说实时绘图,不要每帧通知 JS,让 Native 直接在画布上画。

数据压缩、序列化优化

传大 JSON 是卡顿最常见原因。

你可以:

  1. 用 protobuf / flatbuffers 减少序列化体积
  2. 用 binary 数据代替 JSON
  3. 字段瘦身------只传必要字段,而不是上百个字段塞在一起

实战案例 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 线程,常见卡顿原因:

  1. JS 在处理 Promise 队列
  2. JSON 解析大对象
  3. 高频 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 与原生通信卡顿,核心原因就是:

  1. Bridge 是单通道串行模型,容易被大 JSON 和高频事件堵住
  2. JS 线程承载 UI 更新,压力更大
  3. 高频通信本身就得优化,不管是否使用 JSI

最有效的优化方法:

  • 降低通信频次(批量、节流、合并事件)
  • 数据压缩、减少 JSON 体积
  • 让 Native 承担更多计算
  • 用 JSI / TurboModule 替换 Bridge,实现低延迟通信
  • UI 更新避免不必要的 re-render,让动画走 Native
相关推荐
橙子家5 小时前
浏览器缓存之【基础键值存储】:Local storage 和 Session storage
前端
星星在线8 小时前
MusicFree:一个「All in One」的个人音乐服务器,让听歌回归简单
前端·后端
IT_陈寒8 小时前
Redis的SETNX并发问题让我加了三天班
前端·人工智能·后端
demo007x9 小时前
Docling 文档转换以及技术架构分析
前端·后端·程序员
京东云开发者9 小时前
京东市民服务又“上新”!这次是黑龙江“龙易办”
前端
袋鱼不重10 小时前
我的神奇同事,AI 用多了居然写了个 Open In Codex
前端·后端·ai编程
Fireworks11 小时前
深入vue3源码解读 -- 1、响应式的基础概念
前端
程序员黑豆11 小时前
JDK 下载安装与配置详细教程
java·前端·ai编程
hunterandroid11 小时前
文件存储:内部存储与外部存储
前端
NorBugs11 小时前
飞机大战 Low 版 (Made in AI)
前端