从源码里看懂 React Native 新架构:TurboModule 与 Fabric 到底是怎么跑起来的?

25 阅读11分钟

前言

最近在补 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,还要理解底层调用链里每一层的边界。

因为跨端框架负责提升效率,而原生能力治理,决定稳定性上限。