前言
最近在补 React Native 新架构相关内容时,我发现一个很典型的问题:
很多人能说出 Fabric、TurboModule、JSI、Codegen、Bridgeless 这些关键词,但一旦追问“它们在源码里对应哪里?JS 调原生到底经过哪些对象?Fabric 最后是怎么更新 UIView 的?”就容易停留在概念层。
所以这篇随笔不准备继续抽象地讲“新架构性能更好”,而是换一个角度:
直接从 React Native 源码里,把 TurboModule 和 Fabric 的底层调用关系串起来。
本文基于 React Native 新架构源码阅读,重点回答三个问题:
TurboModuleRegistry.getEnforcing()最终是怎么进入 C++ 的?JSI HostObject / HostFunction在源码里到底长什么样?- Fabric 的
ShadowNode、MountingTransaction最后如何落到真实原生 View?
一、先建立一个正确心智模型
React Native 新架构不是简单地把“JS 调 Native”优化快了一点,而是把过去的 Bridge 模型替换成了:
JS Runtime
↓
JSI
↓
C++ Runtime Layer
↓
Platform Adapter
↓
ObjC / Swift / Java / Kotlin
旧架构的核心是:
JS → Bridge Queue → JSON 序列化 → Native
新架构的核心是:
JS → JSI HostObject / HostFunction → C++ → Native
这也是为什么我们经常说:
新架构不是“没有线程边界”,而是把原来隐式的 Bridge 队列,换成了 JSI + C++ Runtime + 显式调度器。
这个心智模型非常重要。否则很容易误以为 JSI 就等于“JS 可以随便同步调原生”,实际并不是。
二、TurboModule 的 JS 入口:TurboModuleRegistry
先看 JS 侧入口。
源码位置:
Libraries/TurboModule/TurboModuleRegistry.js
核心逻辑非常短:
const NativeModules = require('../BatchedBridge/NativeModules').default;
const turboModuleProxy = global.__turboModuleProxy;
function requireModule(name) {
if (turboModuleProxy != null) {
const module = turboModuleProxy(name);
if (module != null) {
return module;
}
}
const legacyModule = NativeModules[name];
if (legacyModule != null) {
return legacyModule;
}
return null;
}
export function getEnforcing(name) {
const module = requireModule(name);
invariant(
module != null,
`TurboModuleRegistry.getEnforcing(...): '${name}' could not be found.`,
);
return module;
}
这段代码说明了几个关键点:
- JS 侧获取 TurboModule,优先走
global.__turboModuleProxy。 - 如果拿不到,才 fallback 到旧的
NativeModules[name]。 getEnforcing()本身不神秘,它只是一个强校验版本的get()。- 真正的关键在于:
global.__turboModuleProxy是谁注入的?
这就是进入 C++ 的入口。
三、__turboModuleProxy 是 C++ 注入的 HostFunction
继续追源码。
位置:
ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.cpp
核心逻辑在 TurboModuleBinding::install() 里:
runtime.global().setProperty(
runtime,
"__turboModuleProxy",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "__turboModuleProxy"),
1,
[binding = TurboModuleBinding(...)](
jsi::Runtime& rt,
const jsi::Value&,
const jsi::Value* args,
size_t count) {
std::string moduleName = args[0].getString(rt).utf8(rt);
return binding.getModule(rt, moduleName);
}));
这里信息量很大:
__turboModuleProxy不是普通 JS 函数。- 它是通过
jsi::Function::createFromHostFunction创建的。 - 也就是说,它是一个 JSI HostFunction。
- JS 调
__turboModuleProxy("xxx")时,实际执行的是 C++ lambda。 - C++ lambda 里会调用
binding.getModule(rt, moduleName)。
所以 JS 调原生模块的第一跳是:
TurboModuleRegistry.getEnforcing()
↓
global.__turboModuleProxy(name)
↓
JSI HostFunction
↓
TurboModuleBinding::getModule()
这就是“JS 进入 C++”的源码证据。
四、Bridgeless 下还有一个 nativeModuleProxy
有意思的是,在 Bridgeless 模式下,源码里还有一个 BridgelessNativeModuleProxy:
class BridgelessNativeModuleProxy : public jsi::HostObject {
TurboModuleBinding turboBinding_;
std::optional<TurboModuleBinding> legacyBinding_;
public:
jsi::Value get(jsi::Runtime& runtime, const jsi::PropNameID& name) override {
std::string moduleName = name.utf8(runtime);
auto turboModule = turboBinding_.getModule(runtime, moduleName);
if (turboModule.isObject()) {
return turboModule;
}
if (legacyBinding_) {
auto legacyModule = legacyBinding_->getModule(runtime, moduleName);
if (legacyModule.isObject()) {
return legacyModule;
}
}
return jsi::Value::null();
}
};
这里是另一个核心概念:
HostFunction 解决“JS 调一个原生函数”的问题,HostObject 解决“JS 访问一个原生对象属性”的问题。
在 Bridgeless 下,nativeModuleProxy 本质是一个 HostObject。当 JS 访问某个 module 名称时,会触发 C++ 的 get()。
这也解释了为什么新架构里经常说:
JS 以为自己拿到的是普通对象,实际上背后是 C++ HostObject。
五、TurboModuleBinding:把 C++ TurboModule 包成 JS 对象
继续看 TurboModuleBinding::getModule():
jsi::Value TurboModuleBinding::getModule(
jsi::Runtime& runtime,
const std::string& moduleName) const {
std::shared_ptr<TurboModule> module;
module = moduleProvider_(runtime, moduleName);
if (module) {
auto& weakJsRepresentation = module->jsRepresentation_;
if (weakJsRepresentation) {
auto jsRepresentation = weakJsRepresentation->lock(runtime);
if (!jsRepresentation.isUndefined()) {
return jsRepresentation;
}
}
jsi::Object jsRepresentation(runtime);
weakJsRepresentation =
std::make_unique<jsi::WeakObject>(runtime, jsRepresentation);
auto hostObject =
jsi::Object::createFromHostObject(runtime, std::move(module));
jsRepresentation.setProperty(runtime, "__proto__", std::move(hostObject));
return jsRepresentation;
}
return jsi::Value::null();
}
这一段非常关键。
它做了几件事:
- 通过
moduleProvider_找到真正的TurboModule。 - 创建一个 JS object 作为
jsRepresentation。 - 把 C++
TurboModule包成HostObject。 - 把这个
HostObject挂到 JS object 的__proto__上。 - 后续 JS 访问方法时,就会走 HostObject 的属性查找。
所以 TurboModule 的运行时形态可以理解为:
JS Object
↓ __proto__
JSI HostObject
↓
C++ TurboModule
↓
ObjC / Swift / Java / Kotlin
这就是新架构“直接调用”的底层结构。
六、iOS 侧:RCTTurboModuleManager 负责模块查找与缓存
JSI 只负责把 JS 调用引入 C++,真正的平台模块还要继续往下找。
iOS 侧核心文件:
ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm
核心方法:
- (std::shared_ptr<TurboModule>)provideTurboModule:(const char *)moduleName
runtime:(jsi::Runtime *)runtime
{
auto turboModuleLookup = _turboModuleCache.find(moduleName);
if (turboModuleLookup != _turboModuleCache.end()) {
return turboModuleLookup->second;
}
if ([_delegate respondsToSelector:@selector(getTurboModule:jsInvoker:)]) {
auto turboModule = [_delegate getTurboModule:moduleName jsInvoker:_jsInvoker];
if (turboModule != nullptr) {
_turboModuleCache.insert({moduleName, turboModule});
return turboModule;
}
}
id<RCTModuleProvider> module = [self _moduleProviderForName:moduleName];
// ...
}
这里可以看到:
- TurboModule 是懒加载的。
- 加载后会进入
_turboModuleCache。 - iOS 会先找纯 C++ module。
- 再找 ObjC/Swift 平台模块。
_jsInvoker用于把 callback/promise 调回 JS Runtime。
所以 iOS 调用链大致是:
JS
↓
__turboModuleProxy
↓
TurboModuleBinding
↓
RCTTurboModuleManager
↓
ObjCTurboModule
↓
Objective-C / Swift Module
七、iOS 方法队列:methodQueue 仍然很重要
新架构不是完全没有线程问题。
在 RCTTurboModuleManager.mm 里可以看到原生方法调用器:
class ModuleNativeMethodCallInvoker : public NativeMethodCallInvoker {
private:
dispatch_queue_t methodQueue_;
public:
void invokeAsync(const std::string &methodName, std::function<void()> &&work) noexcept override
{
if (methodQueue_ == RCTJSThread) {
work();
return;
}
dispatch_async(methodQueue_, ^{
retainedWork();
});
}
void invokeSync(const std::string &methodName, std::function<void()> &&work) override
{
work();
}
};
这说明:
- 异步原生方法会被派发到对应
methodQueue。 - 如果
methodQueue是 JS 线程,就直接执行。 - 同步方法会直接执行。
- 所以同步 TurboModule 里不能做耗时任务,否则会阻塞 JS。
这是一个很容易被误解的点:
JSI 减少了 Bridge 序列化成本,但没有替你解决耗时任务隔离、线程安全和资源生命周期治理。
八、iOS 类型转换:JSI Value 与 ObjC Object
继续看:
ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm
里面有大量类型转换逻辑,例如 ObjC 转 JSI:
jsi::Value convertObjCObjectToJSIValue(jsi::Runtime &runtime, id value)
{
if ([value isKindOfClass:[NSString class]]) {
return convertNSStringToJSIString(runtime, (NSString *)value);
} else if ([value isKindOfClass:[NSNumber class]]) {
return jsi::Value([value doubleValue]);
} else if ([value isKindOfClass:[NSDictionary class]]) {
return convertNSDictionaryToJSIObject(runtime, (NSDictionary *)value);
} else if ([value isKindOfClass:[NSArray class]]) {
return convertNSArrayToJSIArray(runtime, (NSArray *)value);
} else if (value == (id)kCFNull) {
return jsi::Value::null();
}
return jsi::Value::undefined();
}
以及 JSI 转 ObjC:
id convertJSIValueToObjCObject(
jsi::Runtime &runtime,
const jsi::Value &value,
const std::shared_ptr<CallInvoker> &jsInvoker,
BOOL useNSNull)
{
if (value.isBool()) {
return @(value.getBool());
}
if (value.isNumber()) {
return @(value.getNumber());
}
if (value.isString()) {
return convertJSIStringToNSString(runtime, value.getString(runtime));
}
if (value.isObject()) {
jsi::Object o = value.getObject(runtime);
if (o.isArray(runtime)) {
return convertJSIArrayToNSArray(runtime, o.getArray(runtime), jsInvoker, useNSNull);
}
if (o.isFunction(runtime)) {
return convertJSIFunctionToCallback(runtime, o.getFunction(runtime), jsInvoker);
}
return convertJSIObjectToNSDictionary(runtime, o, jsInvoker, useNSNull);
}
}
这部分源码说明:
- 新架构不是完全没有转换成本。
- 只是它不再走旧 Bridge 的 JSON 序列化队列。
- JS 对象、数组、函数仍然需要映射到平台对象。
- callback 会被包装成原生 block,并通过
CallInvoker回 JS。
这也是为什么大对象频繁跨语言传递仍然要小心。
九、Android 侧:C++ + JNI + Kotlin/Java
Android 链路比 iOS 多一个明显的 JNI 边界。
Kotlin 入口:
ReactAndroid/src/main/java/com/facebook/react/internal/turbomodule/core/TurboModuleManager.kt
文件注释直接说:
/**
* This is the main class and entry point for TurboModules.
* Note that this is a hybrid class, and has a C++ counterpart.
*/
它在初始化时会安装 JSI binding:
init {
dispatchJSBindingInstall(runtimeExecutor)
}
C++ counterpart:
ReactAndroid/src/main/jni/react/turbomodule/ReactCommon/TurboModuleManager.cpp
核心获取模块逻辑:
std::shared_ptr<TurboModule> TurboModuleManager::getTurboModule(
jni::alias_ref<jhybridobject> javaPart,
const std::string& name,
jsi::Runtime& runtime) {
auto turboModuleLookup = turboModuleCache_.find(name);
if (turboModuleLookup != turboModuleCache_.end()) {
return turboModuleLookup->second;
}
auto cxxDelegate = delegate_->cthis();
auto cxxModule = cxxDelegate->getTurboModule(name, jsCallInvoker_);
if (cxxModule) {
turboModuleCache_.insert({name, cxxModule});
return cxxModule;
}
static auto getTurboJavaModule =
javaPart->getClass()->getMethod<jni::alias_ref<JTurboModule>(jstring)>(
"getTurboJavaModule");
auto moduleInstance = getTurboJavaModule(javaPart.get(), jname.get());
if (moduleInstance) {
JavaTurboModule::InitParams params = {
.moduleName = name,
.instance = moduleInstance,
.jsInvoker = jsCallInvoker_,
.nativeMethodCallInvoker = nativeMethodCallInvoker_};
auto turboModule = cxxDelegate->getTurboModule(name, params);
turboModuleCache_.insert({name, turboModule});
return turboModule;
}
return nullptr;
}
这条链路可以总结为:
JS
↓
__turboModuleProxy
↓
C++ TurboModuleBinding
↓
C++ TurboModuleManager
↓
JNI
↓
Kotlin TurboModuleManager.getTurboJavaModule()
↓
Java / Kotlin NativeModule
所以 Android 不是 JS 直接调用 Kotlin,而是经过 JSI、C++、JNI,再进入 Java/Kotlin 实例。
十、Fabric 是另一条链路:它不是调方法,而是更新 UI 树
TurboModule 解决的是“JS 调原生能力”。
Fabric 解决的是“JS 描述 UI,原生挂载 View”。
这两条链路并列存在:
React Native New Architecture
├─ TurboModule:JS → Native Method
└─ Fabric:JS → ShadowTree → Native View
Fabric 不应该被理解成“JS 调 View 方法”,而应该理解成:
React 生成声明式 UI 树,Fabric 在 C++ 层维护 ShadowTree,通过 diff 生成 mutation,平台主线程执行挂载。
十一、ShadowNode:Fabric 的 C++ UI 节点
核心文件:
ReactCommon/react/renderer/core/ShadowNode.h
里面可以看到 ShadowNode 持有这些信息:
const Props::Shared &getProps() const;
const std::vector<std::shared_ptr<const ShadowNode>> &getChildren() const;
const SharedEventEmitter &getEventEmitter() const;
Tag getTag() const;
SurfaceId getSurfaceId() const;
它还支持 clone:
std::shared_ptr<ShadowNode> clone(const ShadowNodeFragment &fragment) const;
std::shared_ptr<ShadowNode> cloneTree(
const ShadowNodeFamily &shadowNodeFamily,
const std::function<std::shared_ptr<ShadowNode>(
const ShadowNode &oldShadowNode)> &callback) const;
这就是 Fabric 很重要的特征:
- ShadowNode 是 C++ 节点。
- Props、Children、EventEmitter、State 都在 ShadowNode 上。
- 更新不是直接修改旧节点,而是 clone 新节点。
- 旧树和新树之间通过结构共享降低成本。
这套模型和 React Fiber 的思想是相通的:
不直接改旧树,而是构建新树,然后在 commit 阶段计算差异。
十二、MountingCoordinator:从 ShadowTree 变成 Mutation
Fabric 不是拿到 ShadowNode 后直接更新 View。
它会先生成一个挂载事务。
源码位置:
ReactCommon/react/renderer/mounting/MountingCoordinator.cpp
关键方法:
std::optional<MountingTransaction> MountingCoordinator::pullTransaction(
bool willPerformAsynchronously) const {
auto transaction = std::optional<MountingTransaction>{};
if (lastRevision_.has_value()) {
number_++;
auto telemetry = lastRevision_->telemetry;
auto mutations = calculateShadowViewMutations(
*baseRevision_.rootShadowNode,
*lastRevision_->rootShadowNode);
transaction = MountingTransaction{
surfaceId_, number_, std::move(mutations), telemetry};
}
return transaction;
}
这里的核心是:
calculateShadowViewMutations(oldTree, newTree)
它会根据旧 ShadowTree 和新 ShadowTree 计算出一组 ShadowViewMutation。
这些 mutation 才是真正要给平台层执行的指令。
大概包括:
Create
Delete
Insert
Remove
Update
所以 Fabric 的链路是:
React Render
↓
ShadowTree Revision
↓
MountingCoordinator
↓
calculateShadowViewMutations
↓
MountingTransaction
↓
Platform MountingManager
十三、iOS Fabric:RCTMountingManager 最终更新 UIView
iOS 端最终落地在:
React/Fabric/Mounting/RCTMountingManager.mm
核心方法是 RCTPerformMountInstructions:
static void RCTPerformMountInstructions(
const ShadowViewMutationList &mutations,
RCTComponentViewRegistry *registry,
RCTMountingTransactionObserverCoordinator &observerCoordinator,
SurfaceId surfaceId)
{
for (const auto &mutation : mutations) {
switch (mutation.type) {
case ShadowViewMutation::Create: {
auto &newChildShadowView = mutation.newChildShadowView;
auto &newChildViewDescriptor =
[registry dequeueComponentViewWithComponentHandle:newChildShadowView.componentHandle
tag:newChildShadowView.tag];
break;
}
case ShadowViewMutation::Insert: {
auto &newChildShadowView = mutation.newChildShadowView;
auto &newChildViewDescriptor =
[registry componentViewDescriptorWithTag:newChildShadowView.tag];
auto &parentViewDescriptor =
[registry componentViewDescriptorWithTag:mutation.parentTag];
UIView<RCTComponentViewProtocol> *newChildComponentView =
newChildViewDescriptor.view;
[newChildComponentView updateProps:newChildShadowView.props oldProps:nullptr];
[newChildComponentView updateEventEmitter:newChildShadowView.eventEmitter];
[newChildComponentView updateState:newChildShadowView.state oldState:nullptr];
[newChildComponentView updateLayoutMetrics:newChildShadowView.layoutMetrics
oldLayoutMetrics:EmptyLayoutMetrics];
[newChildComponentView finalizeUpdates:RNComponentViewUpdateMaskAll];
[parentViewDescriptor.view mountChildComponentView:newChildComponentView
index:mutation.index];
break;
}
case ShadowViewMutation::Update: {
auto &oldChildShadowView = mutation.oldChildShadowView;
auto &newChildShadowView = mutation.newChildShadowView;
auto &newChildViewDescriptor =
[registry componentViewDescriptorWithTag:newChildShadowView.tag];
UIView<RCTComponentViewProtocol> *newChildComponentView =
newChildViewDescriptor.view;
if (oldChildShadowView.props != newChildShadowView.props) {
[newChildComponentView updateProps:newChildShadowView.props
oldProps:oldChildShadowView.props];
}
if (oldChildShadowView.layoutMetrics != newChildShadowView.layoutMetrics) {
[newChildComponentView updateLayoutMetrics:newChildShadowView.layoutMetrics
oldLayoutMetrics:oldChildShadowView.layoutMetrics];
}
break;
}
}
}
}
这里就非常直观了:
Create:从RCTComponentViewRegistry里取/创建组件 View。Insert:设置 props、eventEmitter、state、layout,然后挂到父 View。Update:比较 props、state、layoutMetrics,按需更新。Remove/Delete:从父 View 移除或回收到 registry。
也就是说 Fabric 最终更新原生 View 的过程不是玄学,它就是一组 mutation 指令在主线程被执行。
十四、把 TurboModule 和 Fabric 放在一起看
现在可以把两条链路放在一起对比。
TurboModule 链路
JS 调用 Native Module
↓
TurboModuleRegistry.getEnforcing()
↓
global.__turboModuleProxy(name)
↓
JSI HostFunction
↓
TurboModuleBinding::getModule()
↓
C++ TurboModule HostObject
↓
iOS: RCTTurboModuleManager / RCTTurboModule
↓
Android: TurboModuleManager.cpp / JavaTurboModule.cpp
↓
ObjC / Swift / Java / Kotlin
它的关键词是:
能力调用、JSI、HostFunction、HostObject、CallInvoker、懒加载、模块缓存
Fabric 链路
JSX
↓
React Reconciler
↓
Fabric Renderer
↓
C++ ShadowNode
↓
ShadowTree Revision
↓
MountingCoordinator
↓
ShadowViewMutation
↓
RCTMountingManager / Android MountingManager
↓
UIView / Android View
它的关键词是:
UI 渲染、ShadowTree、Props Diff、Yoga Layout、MountingTransaction、View Mutation
两者不是谁调用谁,而是并列能力:
React Native New Architecture
├─ TurboModule:解决 JS 调原生能力
├─ Fabric:解决 JS 声明 UI 到原生 View
├─ JSI:统一 JS Runtime 与 C++ 的连接方式
└─ Codegen:统一 JS 与 Native 的类型契约
十五、源码阅读后的几个关键理解
1. JSI 不是没有成本
JSI 消除了旧 Bridge 的 JSON 序列化和批量队列,但它不等于零成本。
仍然有:
- JS value 与 ObjC/Java object 的转换。
- 跨线程调度。
- JNI 边界。
- callback/promise 生命周期管理。
- JS Runtime 线程亲和性。
所以大对象频繁跨语言传递,依然可能成为性能问题。
2. 同步 TurboModule 不是天然高性能
同步调用的优势是延迟低,但风险是:
JS Thread 直接等待 Native 返回
如果同步方法里做了:
- 文件 IO
- 网络等待
- 大 JSON 解析
- 锁等待
- 数据库操作
那它依然会卡 JS 线程。
所以同步方法适合:
- 读少量配置。
- 读内存态状态。
- 做轻量计算。
- 获取初始化常量。
不适合承载重业务逻辑。
3. Fabric 的优化点在“更细粒度的提交”
Fabric 并不是魔法。
它的核心收益来自:
- C++ ShadowTree。
- 不可变节点 + clone。
- Props / State / Layout 分层更新。
- MountingTransaction。
- 平台主线程只执行最终 mutation。
- View registry 支持复用。
它把“UI 更新”拆成了更清晰的阶段:
Render → Commit → Mount
这比旧架构里通过 Bridge 批量发 UI 指令更可控。
4. Bridgeless 不等于没有兼容层
从源码里也能看到,Bridgeless 下仍然有 legacy fallback,例如:
std::optional<TurboModuleBinding> legacyBinding_;
这说明 RN 新架构不是“一刀切推倒重来”,而是在新通道上保留了一定兼容层。
这也解释了很多现象:
- 老的
NativeModules有时还能工作。 - 某些
RCTBridgeModule在 Bridgeless 下仍可能被初始化。 - 但不应该再依赖旧 Bridge 作为主通信入口。
结语
这次读源码最大的收获是:
React Native 新架构不是几个 buzzword 的堆叠,而是一套非常清晰的运行时分层。
TurboModule 把“能力调用”拆成:
JS 契约 → JSI Binding → C++ Module → 平台 Adapter → Native 实现
Fabric 把“UI 渲染”拆成:
JSX → Fiber → ShadowNode → ShadowTree Diff → MountingTransaction → Native View
这套架构的价值不只是性能提升,更重要的是把跨语言通信、UI 渲染、模块加载、线程调度这些问题变得更显式。
但同样要注意:
新架构减少了 Bridge 的历史包袱,却没有替业务代码自动解决线程安全、生命周期治理和耗时任务隔离。
真正写好 RN 新架构代码,不能只会写 Spec 和跑 Codegen,还要理解底层调用链里每一层的边界。
因为跨端框架负责提升效率,而原生能力治理,决定稳定性上限。