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
相关推荐
GISer_Jing7 小时前
2025年FE_Jinger的年度总结、经验分享与展望
前端·经验分享·面试·前端框架·aigc
.try-7 小时前
css直线中间小三角
前端·css·html
Dreamcatcher_AC7 小时前
Node.js留言板开发全流程解析
前端·javascript·mysql·node.js·express
鹏程十八少7 小时前
Android 一套代码适配车机/手机横竖屏?看我如何用搞定小米、比亚迪、蔚来、理想、多品牌架构设计
android·前端·面试
持续升级打怪中7 小时前
从前端到大模型:我的AI转型之路与实战思考
前端·人工智能
LYFlied7 小时前
【性能优化】图片渲染性能优化全流程方案详解
前端·性能优化
『六哥』7 小时前
零基础搭建完成完整的前后端分离项目的准备工作
前端·后端·项目开发
沛沛老爹8 小时前
Web开发者实战AI Agent:基于Dify实现OpenAI Deep Research智能体
前端·人工智能·gpt·agent·rag·web转型
冬奇Lab8 小时前
【Cursor进阶实战·01】Figma设计稿一键还原:Cursor + MCP让前端开发提速10倍
android·前端·后端·个人开发·figma
JarvanMo8 小时前
用 Flutter、SwiftUI 和 Compose 写同一个界面:一份真实开发者的实测报告
前端