React Native 渲染原理深度解析:从 JS 到 Native 视图的完整旅程
你写的是 React 代码,屏幕上出现的却是原生 View。中间到底发生了什么? 本文从架构层面拆解 RN 的渲染机制,重点讲清楚三件事:哪些和 React Web 一样,哪些是 RN 独有的,以及 JS 和 Native 之间到底怎么通信。
目录
- RN 整体架构:三棵树、三个线程
- 与 React Web 共用的部分
- RN 独有的部分:没有 DOM,只有 Native
- RN 应用的启动流程
- Render 阶段:构建 Fiber 树(和 React Web 几乎一样)
- Commit 阶段:从 Fiber 到 Native 视图(RN 的核心差异)
- 老架构:Bridge 通信模型
- 新架构:JSI + Fabric + TurboModules
- 事件处理与更新流程
- 总结:一张图串联全流程
1. RN 整体架构:三棵树、三个线程
React Native 的渲染流程可以用”三棵树”来概括:
你写的代码 React 运行时 C++ 层 Native 层
┌──────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ JSX │ Babel │ Fiber Tree │ 指令 │ Shadow Tree │ 映射 │ Native Tree │
│ View/Text│ ────────→ │ (虚拟 DOM) │ ─────→ │ (布局计算) │ ───→ │ (真实视图) │
└──────────┘ 编译 └──────────────┘ └──────────────┘ └──────────────┘
│ │ │
JS 线程 C++ 线程 Native 主线程
三棵树各自的职责:
Fiber Tree(JS 线程):
- 和 React Web 完全一样的虚拟 DOM 树
- 通过 Reconciler 进行 diff,打上 PLACEMENT / UPDATE / DELETION 标记
- Hooks(useState、useEffect)都在这一层运行
Shadow Tree(C++ 线程):
- RN 独有,React Web 没有这一层
- 接收 Fiber Tree 的渲染指令,构建布局树
- 使用 Yoga 引擎计算 Flexbox 布局(位置、大小)
- 跨平台:iOS 和 Android 共用同一份 Shadow Tree
Native Tree(Native 主线程):
- iOS:UIView 层级树
- Android:android.view.View 层级树
- 根据 Shadow Tree 的布局结果,创建和更新真实的原生视图
为什么需要三棵树?因为 JS 不能直接操作原生视图(不像浏览器里可以直接操作 DOM),中间需要一个翻译层。Shadow Tree 就是这个翻译层——它既能被 JS 侧描述,又能被 Native 侧消费。
1.1 为什么 Web 端没有 Shadow Tree?
这是理解 RN 架构的关键问题。
在 React Web 中,布局计算是浏览器自动完成的:
React Web 的流程:
Fiber Tree (JS)
│
▼ commitWork
DOM Tree (浏览器)
│ 浏览器自动完成以下工作:
├─ CSS 解析
├─ 布局计算(Layout):每个元素的 x, y, width, height
├─ 绘制(Paint)
└─ 合成(Composite)
你写 <div style={{width: 100, padding: 20}}>
浏览器自动算出:这个 div 宽 100px,内容区 60px,位置在 (x, y)
React 不需要关心布局计算,浏览器全包了。
但在 RN 中,没有浏览器!iOS 的 UIKit 和 Android 的 View 系统各有各的布局方式,而且 JS 线程和 Native 主线程是隔离的。如果让 Native 自己算布局,会有两个问题:
问题 1:跨平台不一致
同样的 style={{flexDirection: 'row', justifyContent: 'center'}}
iOS 的 Auto Layout 和 Android 的 LinearLayout 算出来的结果可能不一样
→ 需要一个统一的布局引擎
问题 2:布局信息回传开销
如果 Native 算完布局,JS 侧想知道某个元素的位置和大小
→ 需要通过 Bridge 异步回传,延迟大
→ 新架构中 C++ 算布局,JS 通过 JSI 同步获取,延迟极小
Shadow Tree 就是为了解决这些问题而存在的:
React Native 的流程:
Fiber Tree (JS 线程)
│ 描述"要渲染什么"
│
▼ createView / setChildren 指令
Shadow Tree (C++ 线程)
│ 做三件事:
│ ① 用 Yoga 引擎统一计算 Flexbox 布局(跨平台一致)
│ ② 确定每个节点的 x, y, width, height
│ ③ 把布局结果传给 Native
│
▼ 布局结果
Native Tree (Native 主线程)
│ 只负责"按照布局结果创建和摆放视图"
│ 不需要自己算布局
└─ iOS: UIView.frame = CGRect(x, y, width, height)
Android: view.layout(left, top, right, bottom)
用一个具体例子对比:
// 你写的样式
<View style={{ flexDirection: 'row', padding: 10 }}>
<Text style={{ flex: 1 }}>左边</Text>
<Text style={{ flex: 2 }}>右边</Text>
</View>
React Web:
→ 浏览器拿到 DOM + CSS
→ 浏览器的布局引擎自动计算:
View: x=0, y=0, width=375, height=40
左边: x=10, y=10, width=115, height=20
右边: x=125, y=10, width=230, height=20
→ 浏览器自动绘制
React Native:
→ JS 发送 createView 指令给 C++/Native
→ Shadow Tree 中的 Yoga 引擎计算 Flexbox 布局:
RCTView: x=0, y=0, width=375, height=40
RCTText"左边": x=10, y=10, width=115, height=20
RCTText"右边": x=125, y=10, width=230, height=20
→ 把计算结果传给 Native
→ Native 按照坐标创建和摆放 UIView/android.view.View
一句话总结:Shadow Tree 就是 RN 的”浏览器布局引擎”。Web 端浏览器自带布局能力,所以不需要 Shadow Tree;RN 没有浏览器,需要自己用 Yoga 算布局,Shadow Tree 就是承载这个计算的中间层。
1.2 Yoga 引擎如何统一 iOS 和 Android 的布局?
你可能注意到了,iOS 和 Android 设置布局的 API 参数完全不同:
iOS: UIView.frame = CGRect(x, y, width, height) // 用 x, y, 宽, 高
Android: view.layout(left, top, right, bottom) // 用 左, 上, 右, 下
但这不影响 Yoga 的统一性。因为 Yoga 输出的是一套平台无关的标准结果,各平台只需要做一层简单的数值转换:
Yoga 引擎的输出(C++ 层,平台无关):
每个 Shadow Node 计算完后,都有这四个值:
node.layout.left = 10 // 相对于父节点的左偏移
node.layout.top = 20 // 相对于父节点的上偏移
node.layout.width = 200 // 宽度
node.layout.height = 40 // 高度
这四个值在 iOS 和 Android 上完全一样——Yoga 是 C++ 写的,
同一份代码编译到两个平台,计算结果一致。
各平台拿到 Yoga 的结果后,做一层简单转换:
iOS 的转换:
// Yoga 输出 → CGRect
view.frame = CGRect(
x: node.layout.left, // 10
y: node.layout.top, // 20
width: node.layout.width, // 200
height: node.layout.height // 40
)
Android 的转换:
// Yoga 输出 → left/top/right/bottom
view.layout(
left: node.layout.left, // 10
top: node.layout.top, // 20
right: node.layout.left + node.layout.width, // 10 + 200 = 210
bottom: node.layout.top + node.layout.height // 20 + 40 = 60
)
整个流程:
你写的样式 Yoga(C++ 层) 平台适配层
style={{ 统一计算 Flexbox 简单数值转换
flexDirection: 'row', → left=10, top=20, → iOS: CGRect(10,20,200,40)
padding: 10, width=200, height=40 Android: layout(10,20,210,60)
flex: 1 ↑
}} ↑ 只是换了一种表达方式
这一步 iOS/Android 结果完全一样 数值本质上是一样的
所以 Yoga 的统一性体现在:布局计算只做一次,在 C++ 层完成,结果是平台无关的四个数值(left, top, width, height)。iOS 和 Android 的 API 参数虽然不同,但只是同一组数值的不同表达方式,转换是无损的。
2. 与 React Web 共用的部分
很多人以为 RN 是一套全新的框架。实际上,RN 和 React Web 共享了大量核心代码。
2.1 完全共用的部分
┌─────────────────────────────────────────────────┐
│ React 核心 │
│ │
│ ✅ createElement / JSX 编译 │
│ ✅ Hooks(useState, useEffect, useRef...) │
│ ✅ Component 基类(setState, forceUpdate) │
│ ✅ Context(createContext, useContext) │
│ ✅ Fiber 架构(虚拟 DOM 节点结构) │
│ ✅ Reconciler(diff 算法,打标记) │
│ ✅ 调度器(时间分片,优先级) │
│ │
│ 这些代码 RN 和 Web 用的是同一个 react 包 │
└─────────────────────────────────────────────────┘
也就是说,你在 Mini React 中实现的 createElement、useState、useEffect、Fiber 链表遍历、Reconcile diff——这些在 RN 中原理完全一样。
2.2 不同的部分
React Web React Native
┌─────────────────┐ ┌─────────────────────┐
渲染器 │ react-dom │ │ react-native │
│ (操作 DOM) │ │ (发送 Native 指令) │
└─────────────────┘ └─────────────────────┘
宿主组件 │ div, span, p │ │ View, Text, Image │
事件系统 │ 浏览器事件 │ │ Native 触摸事件 │
布局引擎 │ 浏览器 CSS 引擎 │ │ Yoga (Flexbox) │
渲染目标 │ DOM 树 │ │ Native View 树 │
一句话总结:React 负责”算出什么变了”,渲染器负责”把变化应用到哪里”。Web 应用到 DOM,RN 应用到 Native View。
3. RN 独有的部分:没有 DOM,只有 Native
3.1 组件映射:View 不是 div
在 React Web 中,你写 <div>,浏览器直接创建一个 DOM 元素。
在 RN 中,你写 <View>,最终会变成原生视图:
// 你写的 RN 代码
import { View, Text } from 'react-native'
function App() {
return (
<View style={{ padding: 20 }}>
<Text>Hello React Native</Text>
</View>
)
}
RN 组件 → Native 视图的映射关系:
<View> → iOS: UIView / Android: android.view.View
<Text> → iOS: UILabel / Android: TextView
<Image> → iOS: UIImageView / Android: ImageView
<ScrollView> → iOS: UIScrollView / Android: ScrollView
<TextInput> → iOS: UITextField / Android: EditText
3.2 View 组件的底层实现
<View> 看起来像一个普通 React 组件,但它的底层指向了一个 Native 组件名称:
// RN 源码 Libraries/Components/View/View.js(简化)
const View = React.forwardRef((props, forwardedRef) => {
return (
<TextAncestor.Provider value={false}>
<ViewNativeComponent {...props} />
</TextAncestor.Provider>
)
})
ViewNativeComponent 的本质是一个注册了 RCTView 名称的原生组件:
// ViewNativeComponent 最终会被标记为 HostComponent
// 在 Fiber 中,它的 tag 是 HostComponent
// 它的 viewConfig.uiViewClassName = 'RCTView'
当 Reconciler 处理到这个 Fiber 节点时,不会像 Web 那样调用 document.createElement('div'),而是发送一条指令给 Native:
// Web 的做法:
document.createElement('div') → 直接创建 DOM 节点
// RN 的做法:
UIManager.createView(tag, 'RCTView', rootTag, props) → 发指令给 Native
Native 创建 UIView
这就是 RN 最核心的差异:JS 侧不创建视图,只发指令;Native 侧接收指令,创建真实视图。
4. RN 应用的启动流程
4.1 应用注册
RN 应用的入口和 React Web 不同。Web 用 ReactDOM.render(),RN 用 AppRegistry.registerComponent():
// React Web 入口
import ReactDOM from 'react-dom'
ReactDOM.render(<App />, document.getElementById('root'))
// React Native 入口
import { AppRegistry } from 'react-native'
import App from './App'
AppRegistry.registerComponent('MyApp', () => App)
registerComponent 并不会立即渲染,它只是把组件注册到一个全局表里,等 Native 来启动:
// RN 源码 Libraries/ReactNative/AppRegistry.js(简化)
const runnables: Record<string, any> = {}
const AppRegistry = {
// 注册应用(JS 侧调用)
registerComponent(appKey, componentProvider) {
runnables[appKey] = {
componentProvider,
run: (appParameters) => {
renderApplication(componentProvider(), appParameters.initialProps)
}
}
},
// 运行应用(Native 侧调用)
runApplication(appKey, appParameters) {
runnables[appKey].run(appParameters)
}
}
4.2 Native 启动 RN 应用
总体时序
① Native 应用启动(iOS AppDelegate / Android MainActivity)
│
▼
② 创建 RN 容器视图(iOS RCTRootView / Android ReactRootView)
│
▼
③ 初始化 React 上下文(创建 JS 引擎、NativeModule 注册表、Bridge/JSI)
│
▼
④ 加载并执行 JS Bundle(你写的所有代码编译后的产物)
│
▼
⑤ JS 执行 AppRegistry.registerComponent('MyApp', () => App)
→ 把 App 组件注册到 runnables 表中
│
▼
⑥ Native 通过 Bridge/JSI 调用 AppRegistry.runApplication('MyApp', { initialProps })
→ 触发 renderApplication
│
▼
⑦ renderApplication 内部创建容器组件,进入 React Reconciler 流程
Android 端启动代码
Android 端的启动入口是 ReactActivity → ReactRootView:
// Android 端:MainActivity.java(你的 RN 项目中)
public class MainActivity extends ReactActivity {
@Override
protected String getMainComponentName() {
return "MyApp"; // 对应 JS 侧 registerComponent 的第一个参数
}
}
ReactActivity 内部会创建 ReactRootView,这是承载整个 RN 应用的 Android 视图容器:
// Android 端:ReactRootView.java(RN 源码)
// ReactRootView 继承自 FrameLayout,是一个普通的 Android 视图
public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
// ...
}
ReactRootView 的 startReactApplication 方法是 Native 侧启动 RN 的核心入口:
// Android 端:ReactRootView.java
public void startReactApplication(
ReactInstanceManager reactInstanceManager, // React 实例管理器
String moduleName, // 应用名称,如 "MyApp"
@Nullable Bundle initialProperties, // 初始化参数
@Nullable String initialUITemplate) {
// ① 创建 React JS 上下文(JS 引擎、Bridge、NativeModule 注册表)
mReactInstanceManager.createReactContextInBackground();
// ② 将 ReactRootView 和 reactInstanceManager 关联
Assertions.assertNotNull(mReactInstanceManager).attachRootView(this);
}
createReactContextInBackground 会在一个新的 Java 线程中初始化整个 RN 环境:
// Android 端:ReactInstanceManager.java
// 在后台线程中创建 React 上下文
new Thread(null, new Runnable() {
public void run() {
// 创建 JS 运行上下文
reactApplicationContext = createReactContext(
initParams.getJsExecutorFactory().create(), // JS 执行器(Hermes/JSC)
initParams.getJsBundleLoader() // JS Bundle 加载器
);
}
})
createReactContext 是初始化的核心,做了四件事:
// Android 端:ReactInstanceManager.java
private ReactApplicationContext createReactContext(
JavaScriptExecutor jsExecutor,
JSBundleLoader jsBundleLoader) {
// ① 初始化 NativeModule 注册表(收集所有 @ReactMethod 标注的方法)
NativeModuleRegistry nativeModuleRegistry =
processPackages(reactContext, mPackages, false);
// ② 创建 CatalystInstance(通信管理类,连接 JS ↔ C++ ↔ Native)
catalystInstance = catalystInstanceBuilder.build();
// ③ 将 CatalystInstance 赋值到 ReactContext 中
reactContext.initializeWithInstance(catalystInstance);
// ④ 加载并执行 JS Bundle
catalystInstance.runJSBundle();
return reactContext;
}
其中 CatalystInstance 是整个通信的核心枢纽(在 7.5 节有详细介绍),它的构造函数会初始化 Bridge:
// Android 端:CatalystInstanceImpl.java
private CatalystInstanceImpl() {
mHybridData = initHybrid();
// 创建线程配置
mReactQueueConfiguration = ReactQueueConfigurationImpl.create(
reactQueueConfigurationSpec, new NativeExceptionHandler()
);
// 初始化桥(把 NativeModule 信息传递给 C++ 层)
initializeBridge(
new BridgeCallback(this),
jsExecutor,
mReactQueueConfiguration.getJSQueueThread(),
mNativeModulesQueueThread,
mNativeModuleRegistry.getJavaModules(this),
mNativeModuleRegistry.getCxxModules()
);
}
JS Bundle 执行完毕后,AppRegistry.registerComponent 已经把应用注册好了。接下来 Native 调用 runApplication:
// Android 端:ReactRootView.java
// attachRootView 之后,Native 调用 JS 的 runApplication
@Override
public void runApplication() {
catalystInstance
.getJSModule(AppRegistry.class)
.runApplication(jsAppModuleName, appParams);
// 这一步通过 Bridge/JSI 调用 JS 侧的 AppRegistry.runApplication
// 详细调用链见 7.5 节 "Native → JS 的完整调用链"
}
iOS 端启动代码
iOS 端的启动入口是 AppDelegate:
// iOS 端:AppDelegate.m(你的 RN 项目中)
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// ① 创建 RCTBridge(通信管理类,对应 Android 的 CatalystInstance)
RCTBridge *bridge = [[RCTBridge alloc]
initWithDelegate:self
launchOptions:launchOptions];
// ② 创建 RCTRootView(承载 RN 应用的 iOS 视图,对应 Android 的 ReactRootView)
RCTRootView *rootView = [[RCTRootView alloc]
initWithBridge:bridge
moduleName:@"MyApp" // 对应 JS 侧 registerComponent 的第一个参数
initialProperties:nil];
// ③ 设置为窗口的根视图
self.window.rootViewController.view = rootView;
[self.window makeKeyAndVisible];
return YES;
}
// 指定 JS Bundle 的位置
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
// 开发环境:从 Metro 开发服务器加载
// 生产环境:从本地 main.jsbundle 加载
return [[RCTBundleURLProvider sharedSettings]
jsBundleURLForBundleRoot:@"index"];
}
RCTBridge 初始化时会做和 Android 类似的事情:
// iOS 端:RCTBridge.m(RN 源码,简化)
- (void)setUp {
// ① 创建 JS 执行线程
_jsThread = [[NSThread alloc] initWithTarget:self
selector:@selector(runJSRunLoop) object:nil];
// ② 初始化 NativeModule 注册表
[self registerModules];
// ③ 加载并执行 JS Bundle
[self loadSource:^(NSError *error, RCTSource *source) {
[self executeSourceCode:source.data];
}];
}
JS Bundle 执行完毕后,RCTRootView 会触发 runApplication:
// iOS 端:RCTRootView.m(RN 源码,简化)
- (void)runApplication:(RCTBridge *)bridge {
// 通过 Bridge 调用 JS 侧的 AppRegistry.runApplication
[bridge enqueueJSCall:@"AppRegistry"
method:@"runApplication"
args:@[moduleName, appParameters]
completion:NULL];
}
Android 和 iOS 启动流程对比
Android iOS
──────────────────────────────────────────────────────────────────
入口 MainActivity AppDelegate
(extends ReactActivity) didFinishLaunchingWithOptions
容器视图 ReactRootView RCTRootView
(extends FrameLayout) (extends UIView)
通信管理 CatalystInstanceImpl RCTBridge
initializeBridge() setUp()
JS 实例管理 ReactInstanceManager RCTBridge(合并了)
createReactContext()
初始化线程 new Thread() { createReactContext } NSThread runJSRunLoop
NativeModule NativeModuleRegistry RCTModuleData
注册表 processPackages() registerModules
加载 Bundle catalystInstance.runJSBundle() executeSourceCode()
启动 RN 应用 catalystInstance bridge
.getJSModule(AppRegistry.class) enqueueJSCall("AppRegistry",
.runApplication(name, params) "runApplication", args)
──────────────────────────────────────────────────────────────────
最终都是调用 JS 侧的 AppRegistry.runApplication → renderApplication → React Reconciler
进入 JS 侧:renderApplication
Native 调用 runApplication 后,JS 侧的 AppRegistry 从 runnables 中找到对应的应用并执行 renderApplication:
// RN 源码 Libraries/ReactNative/renderApplication.js(简化)
function renderApplication(RootComponent, initialProps) {
// 创建容器组件
const renderable = (
<PerformanceLoggerContext.Provider>
<AppContainer>
<RootComponent {...initialProps} />
</AppContainer>
</PerformanceLoggerContext.Provider>
)
// 进入渲染流程(相当于 React Web 的 ReactDOM.render)
Renderer.renderElement({
element: renderable,
rootTag,
useFabric: Boolean(fabric), // 是否使用新架构
})
}
// renderElement 根据架构选择不同的渲染器
function renderElement({ element, rootTag, useFabric }) {
if (useFabric) {
// 新架构:Fabric 渲染器
require('../Renderer/shims/ReactFabric').render(element, rootTag)
} else {
// 老架构:传统渲染器
require('../Renderer/shims/ReactNative').render(element, rootTag)
}
}
从这里开始,就进入了 React 的 Reconciler 流程——和 Web 端几乎一样。
5. Render 阶段:构建 Fiber 树(和 React Web 几乎一样)
5.1 入口:performSyncWorkOnRoot
无论是 Web 还是 RN,Reconciler 的核心入口都是同一个函数:
// React Reconciler 核心流程(Web 和 RN 共用)
function performSyncWorkOnRoot(root) {
// render 阶段:遍历 Fiber 树,diff 打标记
var exitStatus = renderRootSync(root, lanes)
// commit 阶段:把标记应用到宿主环境
commitRoot(root, workInProgressRootRecoverableErrors, workInProgressTransitions)
// 检查是否还有待处理的更新
ensureRootIsScheduled(root, now())
}
这和 Mini React 中的 workLoop + commitRoot 是同一个思路,只是真实 React 多了优先级调度。
5.2 workLoopSync:逐个处理 Fiber
// React Reconciler(Web 和 RN 共用)
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress)
}
}
每个 Fiber 节点经历两个阶段:
function performUnitOfWork(unitOfWork) {
// ① beginWork:从上往下,创建/更新子 Fiber
var next = beginWork(current, unitOfWork, renderLanes)
if (next === null) {
// ② completeWork:从下往上,收集 effectTag
completeUnitOfWork(unitOfWork)
} else {
workInProgress = next
}
}
遍历顺序(和 Mini React 一样,深度优先):
以这个 RN 组件为例:
<View>
<Text>小程序:《大前端跨端开发指南》</Text>
<View>
<Text>作者:我不是外星人</Text>
</View>
</View>
形成的 Fiber 树:
App (FunctionComponent)
│
▼ child
View (HostComponent, viewName: 'RCTView')
│
▼ child
Text (HostComponent, viewName: 'RCTText') ──sibling──→ View (RCTView)
│ │
▼ child ▼ child
RawText "小程序..." Text (RCTText)
│
▼ child
RawText "作者..."
beginWork 顺序(向下):
App → View → Text → RawText"小程序" →
回溯到 Text → sibling → View → Text → RawText"作者" →
回溯到顶部
completeWork 顺序(向上):
RawText"小程序" → Text → RawText"作者" → Text → View(内) → View(外) → App
5.3 beginWork 中的关键差异
在 beginWork 阶段,RN 和 Web 处理函数组件的方式完全一样:
// 函数组件(App、自定义组件)→ Web 和 RN 完全一样
function updateFunctionComponent(fiber) {
// 执行函数,Hooks 在这里被调用
const children = Component(props)
reconcileChildren(current, fiber, children)
}
但处理宿主组件(HostComponent)时有区别:
// Web 中的 HostComponent:div, span, p
// → fiber.type = "div"
// → 最终调用 document.createElement("div")
// RN 中的 HostComponent:View, Text, Image
// → fiber.type = "RCTView", "RCTText", "RCTImage"
// → 最终发送 createView 指令给 Native
不过在 Render 阶段,两者都只是打标记(flags),不做实际操作。真正的差异在 Commit 阶段。
5.4 Render 阶段小结
Render 阶段 RN 和 Web 的对比:
React Web React Native
─────────────────────────────────────────────────────────
Reconciler ✅ 相同 ✅ 相同
Fiber 结构 ✅ 相同 ✅ 相同
diff 算法 ✅ 相同 ✅ 相同
Hooks 执行 ✅ 相同 ✅ 相同
时间分片/优先级 ✅ 相同 ✅ 相同
HostComponent type "div","span" "RCTView","RCTText"
─────────────────────────────────────────────────────────
结论:Render 阶段几乎完全一样,差异只在组件名称
6. Commit 阶段:从 Fiber 到 Native 视图(RN 的核心差异)
Commit 阶段是 RN 和 Web 分道扬镳的地方。Web 操作 DOM,RN 发送指令给 Native。
6.1 Web 的 Commit:直接操作 DOM
// React Web 的 commit(简化,和 Mini React 类似)
function commitWork(fiber) {
if (fiber.effectTag === 'PLACEMENT') {
domParent.appendChild(fiber.dom) // 直接操作 DOM
} else if (fiber.effectTag === 'UPDATE') {
updateDom(fiber.dom, oldProps, newProps) // 直接更新 DOM 属性
} else if (fiber.effectTag === 'DELETION') {
domParent.removeChild(fiber.dom) // 直接删除 DOM
}
}
6.2 RN 的 Commit:发送指令给 Native
RN 不能直接操作视图,它通过 UIManager 发送指令:
// RN 老架构的 commit(简化)
function commitWork(fiber) {
if (fiber.flags & Placement) {
// 不是 appendChild,而是发指令!
UIManager.createView(
fiber.tag, // 唯一标识(如 3, 5, 7...)
fiber.viewName, // 组件名(如 'RCTView', 'RCTText')
rootTag, // 根视图标识
fiber.props // 属性(style, children 等)
)
UIManager.setChildren(parentTag, [fiber.tag])
}
else if (fiber.flags & Update) {
UIManager.updateView(
fiber.tag,
fiber.viewName,
changedProps // 只传变化的属性
)
}
else if (fiber.flags & Deletion) {
UIManager.manageChildren(
parentTag,
null, null,
null, null,
[indexToRemove] // 要删除的索引
)
}
}
6.3 图解:一个 RN 组件的完整渲染指令
以这个组件为例:
import { View, Text } from 'react-native'
function App() {
return (
<View>
<Text>小程序:《大前端跨端开发指南》</Text>
<View>
<Text>作者:我不是外星人</Text>
</View>
</View>
)
}
Commit 阶段发出的指令序列:
深度优先遍历 Fiber 树,每遇到 HostComponent 就发指令:
① 遍历到 RawText "小程序..."
JS → Native: UIManager.createView(3, 'RCTRawText', { content: '小程序:《大前端跨端开发指南》' })
② 遍历到 Text
JS → Native: UIManager.createView(5, 'RCTText', {})
JS → Native: UIManager.setChildren(5, [3]) ← 把 RawText 插入 Text
③ 遍历到 RawText "作者..."
JS → Native: UIManager.createView(7, 'RCTRawText', { content: '作者:我不是外星人' })
④ 遍历到内层 Text
JS → Native: UIManager.createView(9, 'RCTText', {})
JS → Native: UIManager.setChildren(9, [7]) ← 把 RawText 插入 Text
⑤ 遍历到内层 View
JS → Native: UIManager.createView(11, 'RCTView', {})
JS → Native: UIManager.setChildren(11, [9]) ← 把 Text 插入 View
⑥ 遍历到外层 View
JS → Native: UIManager.createView(13, 'RCTView', {})
JS → Native: UIManager.setChildren(13, [5, 11]) ← 把 Text 和 View 插入外层 View
Native 侧收到这些指令后:
Android 侧:
tag=3 → new TextView("小程序:《大前端跨端开发指南》")
tag=5 → new ViewGroup() → addView(tag3)
tag=7 → new TextView("作者:我不是外星人")
tag=9 → new ViewGroup() → addView(tag7)
tag=11 → new android.view.View() → addView(tag9)
tag=13 → new android.view.View() → addView(tag5, tag11)
iOS 侧:
tag=3 → [[UILabel alloc] init] setText:@"小程序..."
tag=5 → [[UIView alloc] init] addSubview:tag3
...同理
最终形成 Native View 树(Shadow Tree → Native Tree):
RCTView (tag=13)
├── RCTText (tag=5)
│ └── RCTRawText (tag=3) "小程序:《大前端跨端开发指南》"
└── RCTView (tag=11)
└── RCTText (tag=9)
└── RCTRawText (tag=7) "作者:我不是外星人"
6.4 对比:Web Commit vs RN Commit
React Web React Native
────────────────────────────────────────────────────────────────
新增元素 dom.appendChild(element) UIManager.createView(tag, viewName, props)
UIManager.setChildren(parentTag, [childTags])
更新元素 dom.className = "new" UIManager.updateView(tag, viewName, changedProps)
dom.addEventListener(...)
删除元素 dom.removeChild(element) UIManager.manageChildren(parentTag, ..., [removeIndex])
执行环境 同步,在 JS 主线程直接操作 异步,指令通过 Bridge/JSI 传递给 Native 线程
────────────────────────────────────────────────────────────────
7. 老架构:Bridge 通信模型
在 RN 0.68 之前的老架构中,JS 和 Native 之间通过 Bridge(桥)通信。理解 Bridge 是理解 RN 性能瓶颈的关键。
7.1 Bridge 的本质:JSON 序列化的消息队列
JS 线程 Bridge Native 线程
┌──────────┐ ┌──────────────┐ ┌──────────────┐
│ setState │ JSON 序列化 │ MessageQueue │ JSON 反序列化│ UIManager │
│ → diff │ ──────────→ │ (异步队列) │ ──────────→ │ → createView │
│ → 指令 │ │ │ │ → 渲染视图 │
└──────────┘ └──────────────┘ └──────────────┘
│
所有数据都要经过
JSON.stringify / JSON.parse
每一条指令都要经过序列化:
// JS 侧发出的指令
UIManager.createView(3, 'RCTView', 1, { style: { padding: 20 } })
// 实际通过 Bridge 传递的数据(JSON 序列化)
{
"module": "UIManager",
"method": "createView",
"args": [3, "RCTView", 1, {"style": {"padding": 20}}]
}
// Native 侧收到后反序列化,再执行对应的方法
7.2 Bridge 的三大问题
问题 1:序列化开销
每次通信都要 JSON.stringify + JSON.parse
大量数据(如长列表)传输时,序列化本身就很耗时
JS: { items: [{id:1,name:"..."}, {id:2,name:"..."}, ...1000个] }
↓ JSON.stringify(耗时!)
Bridge: "{"items":[{"id":1,"name":"..."},...]}"
↓ JSON.parse(又耗时!)
Native: 终于拿到数据了...
问题 2:异步通信
JS 和 Native 不能同步调用对方
所有通信都是异步的,放入消息队列等待处理
JS: "我要获取屏幕宽度"
→ 放入队列 → 等待...
Native: "收到,宽度是 375"
→ 放入队列 → 等待...
JS: "终于拿到了"(已经过了好几帧)
问题 3:单通道瓶颈
所有模块的通信共用一个 Bridge
渲染指令、事件回调、网络请求、动画... 全部排队
渲染指令: createView → 排队
触摸事件: onPress → 排队
网络回调: fetch result → 排队
动画帧: animate → 排队(动画卡顿的根源!)
7.3 Commit 阶段如何触发 Bridge 通信
这是老架构最关键的部分。很多人知道”RN 通过 Bridge 通信”,但不清楚具体是在哪一步、怎么触发的。
先回顾一下 React 的两大阶段:
performSyncWorkOnRoot
│
├─ Render 阶段(renderRootSync)
│ workLoopSync → performUnitOfWork
│ ├─ beginWork:向下遍历,执行组件函数,diff 打标记
│ └─ completeWork:向上归并,创建节点实例
│
└─ Commit 阶段(commitRoot → commitRootImpl)
├─ BeforeMutation:更新前(getSnapshotBeforeUpdate)
├─ Mutation:执行 DOM 操作(appendChild / updateView / removeChild)
└─ Layout:更新后(componentDidMount / useLayoutEffect)
在 RN 中,通信发生在两个时机:
时机一:completeWork 阶段 → 创建节点
当 Render 阶段向上归并时,遇到 HostComponent(View、Text 等),会调用 createInstance 创建节点实例。这一步在老架构中就会触发 Bridge 通信:
// 老架构:创建 View 元素
// 来自 RN 源码 ReactNativeRenderer.js(简化)
function createInstance(type, props, rootContainerInstance, hostContext) {
// 通过 Bridge 发送 createView 指令给 Native!
ReactNativePrivateInterface.UIManager.createView(
tag, // reactTag(唯一标识,如 3, 5, 7...)
viewConfig.uiViewClassName, // viewName(如 'RCTView', 'RCTText')
rootContainerInstance, // rootTag(根视图标识)
updatePayload // props(样式、属性等)
)
// 创建 JS 侧的组件引用
var component = new ReactNativeFiberHostComponent(
tag,
viewConfig,
internalInstanceHandle
)
}
// 创建文本节点
function createTextInstance(text, rootContainerInstance, hostContext) {
ReactNativePrivateInterface.UIManager.createView(
tag,
'RCTRawText', // 文本节点固定用 RCTRawText
rootContainerInstance,
{ text: text } // 文本内容作为 props
)
}
这些 UIManager.createView 调用会通过 Bridge 传递给 Native 侧。以 Android 为例:
// Android 端:UIManagerModule.java
@ReactMethod
public void createView(int tag, String className, int rootViewTag,
ReadableMap props) {
// 创建 Shadow Node(用于布局计算)
ReactShadowNode cssNode = createShadowNode(className);
// 将 Shadow Node 添加到注册表
mShadowNodeRegistry.addNode(cssNode);
// 设置样式属性
cssNode.updateProperties(styles);
// 处理创建逻辑
handleCreateView(cssNode, rootViewTag, styles);
}
// createShadowNode 根据 className 找到对应的 ViewManager
protected ReactShadowNode createShadowNode(String className) {
ViewManager viewManager = mViewManagers.get(className);
return viewManager.createShadowNodeInstance(mReactContext);
}
// handleCreateView 最终会生成一个 Operation 放入队列
protected void handleCreateView() {
mNativeViewHierarchyOptimizer.handleCreateView(cssNode,
cssNode.getThemedContext(), styles);
}
// 创建 UI 操作指令,放入操作队列
public void enqueueCreateView() {
mCreateViewCount++;
mNonBatchedOperations.addLast(
new CreateViewOperation(themedContext, viewReactTag,
viewClassName, initialProps)
);
}
iOS 端类似:
// iOS 端:RCTUIManager.m
RCT_EXPORT_METHOD(createView
:(nonnull NSNumber *)reactTag
viewName:(NSString *)viewName
rootTag:(nonnull NSNumber *)rootTag
props:(NSDictionary *)props)
组件名 → Shadow Node → Native 组件的映射关系:
Android:
React 组件 Shadow Node Native 组件
<View> → LayoutShadowNode → android.view.View
<Text> → ReactTextShadowNode → ReactTextView
<Image> → LayoutShadowNode → ReactImageView
iOS:
React 组件 Shadow Node Native 组件
<View> → RCTShadowView → RCTView
<Text> → RCTTextShadowView → RCTTextView
<Image> → RCTImageShadowView → RCTImageView
时机二:Commit Mutation 阶段 → 插入、更新、删除
Render 阶段创建了节点,但还没有建立父子关系。在 Commit 的 Mutation 阶段,会执行 setChildren(插入)、updateView(更新)、manageChildren(删除)等操作:
// 真实 React 的 commitRootImpl — 三个子阶段
function commitRootImpl() {
// BeforeMutation:更新前(getSnapshotBeforeUpdate、调度 useEffect)
commitBeforeMutationEffects(root, transitions)
// Mutation:执行实际的 DOM/Native 操作 ← Bridge 通信在这里!
commitMutationEffectsOnFiber(transitions, root)
// 双缓冲切换
root.current = transitions
// Layout:更新后(componentDidMount、useLayoutEffect)
commitLayoutEffects(transitions, root, lanes)
}
关于这三个子阶段和 Mini React 的
commitRoot的详细对比,见 React Hooks 深度解析 - 第 9.1 节。
Mutation 阶段会根据 Fiber 上的 flags 发送不同的指令:
// 老架构 Mutation 阶段发送的指令(简化)
// 新增子节点:setChildren
// 把 childTag 插入到 parentTag 下
ReactNativePrivateInterface.UIManager.setChildren(parentTag, [childTag1, childTag2])
// 更新节点属性:updateView
// 只传变化的 props
ReactNativePrivateInterface.UIManager.updateView(tag, viewName, changedProps)
// 删除/移动子节点:manageChildren
// moveFrom/moveTo 处理移动,removeFrom 处理删除
ReactNativePrivateInterface.UIManager.manageChildren(
parentTag,
moveFrom, moveTo,
addChildTags, addAtIndices,
removeFrom
)
Android 端接收 updateView 指令的处理:
// Android 端:UIImplementation.java
public void updateView(int tag, String className, ReadableMap props) {
// 从注册表获取 Shadow Node
ReactShadowNode cssNode = mShadowNodeRegistry.getNode(tag);
if (props != null) {
// 更新 Shadow Node 的样式属性
cssNode.updateProperties(styles);
// 处理更新逻辑 → 生成 UpdateViewOperation
handleUpdateView(cssNode, className, styles);
}
}
public void handleUpdateView() {
// 创建更新操作,放入操作队列
mUIViewOperationQueue.enqueueUpdateProperties(
node.getReactTag(), className, props
);
}
完整图解:一次 createView + setChildren 的 Bridge 通信
以这个组件为例:
<View>
<Text>Hello</Text>
</View>
═══ Render 阶段(JS 线程)═══
beginWork:向下遍历
App → View → Text → RawText"Hello"
completeWork:向上归并
RawText"Hello":
→ createTextInstance("Hello")
→ UIManager.createView(3, 'RCTRawText', rootTag, {text: "Hello"})
→ 📤 Bridge 发送: {"module":"UIManager","method":"createView","args":[3,"RCTRawText",...]}
Text:
→ createInstance('RCTText')
→ UIManager.createView(5, 'RCTText', rootTag, {})
→ 📤 Bridge 发送: {"module":"UIManager","method":"createView","args":[5,"RCTText",...]}
View:
→ createInstance('RCTView')
→ UIManager.createView(7, 'RCTView', rootTag, {})
→ 📤 Bridge 发送: {"module":"UIManager","method":"createView","args":[7,"RCTView",...]}
═══ Commit Mutation 阶段(JS 线程)═══
setChildren:建立父子关系
→ UIManager.setChildren(5, [3]) // RawText 插入 Text
→ 📤 Bridge 发送: {"module":"UIManager","method":"setChildren","args":[5,[3]]}
→ UIManager.setChildren(7, [5]) // Text 插入 View
→ 📤 Bridge 发送: {"module":"UIManager","method":"setChildren","args":[7,[5]]}
→ UIManager.setChildren(rootTag, [7]) // View 插入根视图
→ 📤 Bridge 发送: {"module":"UIManager","method":"setChildren","args":[1,[7]]}
═══ Bridge 传输(异步)═══
所有指令被序列化为 JSON,放入 MessageQueue
等待 Native 线程消费
═══ Native 侧(Native 线程)═══
④ 从 MessageQueue 依次取出指令
⑤ createView(3, 'RCTRawText') → 创建 Shadow Node → 放入注册表
⑥ createView(5, 'RCTText') → 创建 Shadow Node → 放入注册表
⑦ createView(7, 'RCTView') → 创建 Shadow Node → 放入注册表
⑧ setChildren(5, [3]) → 建立 Shadow Tree 父子关系
⑨ setChildren(7, [5]) → 建立 Shadow Tree 父子关系
⑩ Yoga 计算布局(位置、大小)
⑪ 生成 UIViewOperation 队列
⑫ 在主线程依次执行 Operation → 创建真实 Native View
最终 Native View 树:
UIView (tag=7)
└── UILabel (tag=5)
└── "Hello" (tag=3)
更新场景:setState 后的 Bridge 通信
假设 Text 内容从 "Hello" 变成 "World"
═══ Render 阶段 ═══
beginWork:重新执行组件函数,diff 发现 Text 内容变了
→ RawText Fiber 打上 Update flag
completeWork:
→ 不需要 createView(节点已存在,复用)
→ 收集变化的 props: { text: "World" }
═══ Commit Mutation 阶段 ═══
处理 Update flag:
→ UIManager.updateView(3, 'RCTRawText', { text: "World" })
→ 📤 Bridge 发送: {"module":"UIManager","method":"updateView","args":[3,"RCTRawText",{"text":"World"}]}
═══ Native 侧 ═══
收到 updateView 指令:
→ 从注册表找到 tag=3 的 Shadow Node
→ 更新 Shadow Node 的 text 属性
→ 重新计算布局(如果文字长度变了,可能影响布局)
→ 生成 UpdateViewOperation
→ 主线程执行:UILabel.setText("World")
屏幕上 "Hello" 变成 "World" ✅
7.4 老架构通信流程总结
通信时机 JS 侧调用 Native 侧处理
─────────────────────────────────────────────────────────────────────────────────────────
创建节点 completeWork 阶段 UIManager.createView() 创建 Shadow Node + 注册
插入子节点 Commit Mutation 阶段 UIManager.setChildren() 建立 Shadow Tree 父子关系
更新节点 Commit Mutation 阶段 UIManager.updateView() 更新 Shadow Node props
删除节点 Commit Mutation 阶段 UIManager.manageChildren() 从 Shadow Tree 移除
─────────────────────────────────────────────────────────────────────────────────────────
所有调用都经过 Bridge → JSON 序列化 → MessageQueue → JSON 反序列化 → Native 执行
7.5 Bridge 底层:JS 调用是如何一步步到达 Java/OC 的?
上面说了”通过 Bridge 通信”,但 Bridge 不是一个黑盒。JS 是怎么调用到 Java/OC 代码的?中间经过了哪些层?
三层架构
┌─────────────────────────────────────────────────────────────┐
│ JS 层 │
│ MessageQueue 类:管理所有 JS ↔ Native 的双向通信 │
│ - _lazyCallableModules:存放 Native 可调用的 JS 模块 │
│ - _successCallbacks:存放异步回调函数 │
│ - enqueueNativeCall():JS → Native 的出口 │
│ - callFunctionReturnFlushedQueue():Native → JS 的入口 │
└──────────────────────────┬──────────────────────────────────┘
│
┌──────────────────────────┴──────────────────────────────────┐
│ C++ 层 │
│ CatalystInstanceImpl:通信管理中枢 │
│ - NativeToJsBridge:负责 Native → JS 方向 │
│ - JsToNativeBridge:负责 JS → Native 方向 │
│ - JSIExecutor:执行 JS 代码,桥接 JS 运行时 │
│ - ModuleRegistry:Native 模块注册表 │
└──────────────────────────┬──────────────────────────────────┘
│
┌──────────────────────────┴──────────────────────────────────┐
│ Native 层(Java / OC) │
│ CatalystInstanceImpl(Java):Native 侧的通信管理类 │
│ - NativeModuleRegistry:所有 NativeModule 的注册表 │
│ - UIManagerModule:处理 UI 渲染指令 │
│ - JavaMethodWrapper:通过反射调用具体的 Native 方法 │
└─────────────────────────────────────────────────────────────┘
初始化:Bridge 是怎么建立的
应用启动时,Native 侧会初始化整个 Bridge 链路:
// Android 端:CatalystInstanceImpl.java
private CatalystInstanceImpl() {
mHybridData = initHybrid();
// 创建线程配置(JS 线程 + NativeModule 线程)
mReactQueueConfiguration = ReactQueueConfigurationImpl.create(
reactQueueConfigurationSpec, new NativeExceptionHandler()
);
// 初始化桥!把 Native 模块信息传递给 C++ 层
initializeBridge(
new BridgeCallback(this), // 回调函数
jsExecutor, // JS 执行器
mReactQueueConfiguration.getJSQueueThread(), // JS 线程
mNativeModulesQueueThread, // NativeModule 线程
mNativeModuleRegistry.getJavaModules(this), // Java 模块列表
mNativeModuleRegistry.getCxxModules() // C++ 模块列表
);
}
C++ 层接收到后,建立双向通信桥:
// C++ 层:CatalystInstanceImpl.cpp
void CatalystInstanceImpl::initializeBridge(...) {
// 创建 NativeModule 线程
moduleMessageQueue_ =
std::make_shared<JMessageQueueThread>(nativeModulesQueue);
// 构建 Native 模块注册表(moduleID → NativeModule 的映射)
moduleRegistry_ = std::make_shared<ModuleRegistry>(
buildNativeModuleList(...)
);
// 初始化 Instance,建立 NativeToJsBridge 和 JsToNativeBridge
instance_->initializeBridge(...);
}
同时,C++ 层会向 JS 运行时注入关键的全局对象和函数:
// C++ 层:JSIExecutor.cpp
void JSIExecutor::initializeRuntime() {
// ① 注入 nativeModuleProxy → JS 通过它访问 NativeModules
runtime_->global().setProperty(
*runtime_,
"nativeModuleProxy",
Object::createFromHostObject(
*runtime_,
std::make_shared<NativeModuleProxy>(nativeModules_)
)
);
// ② 注入 nativeFlushQueueImmediate → JS 通过它把消息队列刷给 Native
runtime_->global().setProperty(
*runtime_,
"nativeFlushQueueImmediate",
Function::createFromHostFunction(
*runtime_,
PropNameID::forAscii(*runtime_, "nativeFlushQueueImmediate"),
1,
[this](jsi::Runtime &, const jsi::Value &,
const jsi::Value *args, size_t count) {
// 调用 callNativeModules,把队列中的指令分发给对应的 NativeModule
callNativeModules(args[0], false);
return Value::undefined();
}
)
);
}
初始化完成后的全景:
JS global 对象上被注入了:
global.nativeModuleProxy → 访问 NativeModules 的入口
global.nativeFlushQueueImmediate → 把消息队列刷给 C++ 的入口
JS 侧 MessageQueue 注册了:
_lazyCallableModules['AppRegistry'] → Native 可以调用 JS 的 AppRegistry
_lazyCallableModules['RCTEventEmitter'] → Native 可以把事件传给 JS
C++ 侧建立了:
NativeToJsBridge → Native → JS 方向的桥
JsToNativeBridge → JS → Native 方向的桥
ModuleRegistry → moduleID → NativeModule 的映射表
JS → Native 的完整调用链(以 UIManager.createView 为例)
JS 侧调用:
UIManager.createView(3, 'RCTView', rootTag, props)
第一步:JS 侧 — genMethod 包装函数触发 enqueueNativeCall
// JS 侧:MessageQueue.js
// 每个 NativeModule 的方法都被 genMethod 包装过
function genMethod(moduleID: number, methodID: number, type: MethodType) {
let fn = function nonPromiseMethodWrapper(...args) {
if (type === 'sync') {
// 同步调用(阻塞,少用)
return BatchedBridge.callNativeSyncHook(moduleID, methodID, newArgs, onFail, onSuccess)
} else {
// 异步调用(常规路径)
BatchedBridge.enqueueNativeCall(moduleID, methodID, newArgs, onFail, onSuccess)
}
}
fn.type = type
return fn
}
// MessageQueue 的 enqueueNativeCall
class MessageQueue {
enqueueNativeCall(moduleID, methodID, params, onFail, onSucc) {
// 把回调函数存起来,等 Native 执行完后回调
this.processCallbacks(moduleID, methodID, params, onFail, onSucc)
// 调用 C++ 注入的 nativeFlushQueueImmediate,把队列刷过去
global.nativeFlushQueueImmediate(queue)
}
}
第二步:C++ 层 — nativeFlushQueueImmediate → callNativeModules → ModuleRegistry
// C++ 层:JSIExecutor.cpp
// nativeFlushQueueImmediate 被调用后:
void callNativeModules() {
// 遍历队列中的所有调用
for (auto &call : methodCalls) {
// 通过 moduleID 找到对应的 NativeModule,调用其 invoke 方法
m_registry->callNativeMethod(
call.moduleId, // 比如 UIManager 的 moduleID
call.methodId, // 比如 createView 的 methodID
std::move(call.arguments),
call.callId
);
}
}
// ModuleRegistry 根据 moduleID 找到模块并调用
void ModuleRegistry::callNativeMethod(moduleId, methodId, params, callId) {
// modules_ 是启动时注册的 NativeModule 数组
// 通过 moduleId 找到对应的模块(如 UIManagerModule)
modules_[moduleId]->invoke(methodId, std::move(params), callId);
}
第三步:Native 层 — JavaMethodWrapper.invoke → 反射调用 Java 方法
// Android 端:JavaMethodWrapper.java
@Override
public void invoke(JSInstance jsInstance, ReadableArray parameters) {
try {
// 通过 Java 反射,调用 NativeModule 上的具体方法
// 比如 UIManagerModule.createView(tag, className, rootViewTag, props)
mMethod.invoke(mModuleWrapper.getModule(), mArguments);
} catch (Exception e) {
// 错误处理
}
}
// 最终到达:UIManagerModule.java
@ReactMethod
public void createView(int tag, String className, int rootViewTag,
ReadableMap props) {
// 创建 Shadow Node,构建 Shadow Tree
mUIImplementation.createView(tag, className, rootViewTag, props);
}
完整调用链图解:
UIManager.createView(3, 'RCTView', rootTag, props)
│
▼ JS 层
genMethod 包装函数
│
▼
MessageQueue.enqueueNativeCall(moduleID, methodID, args)
│ 把回调存入 _successCallbacks
│
▼
global.nativeFlushQueueImmediate(queue)
│
══════════════════ JS → C++ 边界 ══════════════════
│
▼ C++ 层
JSIExecutor::callNativeModules()
│ 遍历 methodCalls
│
▼
ModuleRegistry::callNativeMethod(moduleId, methodId, params)
│ 通过 moduleId 找到 UIManagerModule
│
▼
modules_[moduleId]->invoke(methodId, params)
│
══════════════════ C++ → Java 边界(JNI)══════════════════
│
▼ Java 层
JavaMethodWrapper.invoke()
│ Java 反射
│
▼
UIManagerModule.createView(3, "RCTView", rootTag, props)
│
▼
UIImplementation.createView()
│ 创建 Shadow Node → 注册 → 计算布局 → 生成 Operation
│
▼
UIViewOperationQueue.enqueueCreateView()
│ 放入操作队列
│
▼ 主线程执行
new android.view.View() ← 真实的 Native View 被创建!
Native → JS 的完整调用链(以 runApplication 为例)
反方向也一样,Native 调用 JS 也要经过 C++ 中转:
// Android 端:ReactRootView.java
// Native 启动 RN 应用
public void runApplication() {
catalystInstance
.getJSModule(AppRegistry.class)
.runApplication(jsAppModuleName, appParams);
}
// AppRegistry 是一个 JavaScriptModule 接口
// 通过动态代理,所有方法调用都转发到 invoke
public interface AppRegistry extends JavaScriptModule {
void runApplication(String appKey, WritableMap appParameters);
}
// 动态代理的 invoke 方法
public @Nullable Object invoke() throws Throwable {
// 调用 C++ 层的 callFunction
mCatalystInstance.callFunction(getJSModuleName(), method.getName(), jsArgs);
return null;
}
// C++ 层:Instance.cpp → NativeToJsBridge.cpp → JSIExecutor.cpp
void Instance::callJSFunction(模块, 方法, 参数) {
nativeToJsBridge_->callFunction(模块, 方法, 参数);
}
void NativeToJsBridge::callFunction(模块, 方法, 参数) {
executor->callFunction(模块名称, 方法名称, 参数);
}
void JSIExecutor::callFunction(模块, 方法, 参数) {
// 调用 JS 层 MessageQueue 的 callFunctionReturnFlushedQueue
ret = callFunctionReturnFlushedQueue_->call(
*runtime_, moduleId, methodId,
valueFromDynamic(*runtime_, arguments)
);
// 顺便处理 JS 侧积攒的 Native 调用
callNativeModules(ret, true);
}
// JS 层:MessageQueue.js
class MessageQueue {
// C++ 调用这个方法
callFunctionReturnFlushedQueue(模块名, 方法名, 参数) {
this.__callFunction(模块名, 方法名, 参数)
}
__callFunction(模块名, 方法名, 参数) {
// 从 _lazyCallableModules 找到对应的 JS 模块
const moduleMethods = this.getCallableModule(模块名) // → AppRegistry
// 调用模块上的方法
moduleMethods[方法名].apply(模块, 参数) // → AppRegistry.runApplication(...)
}
}
完整调用链图解:
Native 要启动 RN 应用:runApplication("MyApp", params)
│
▼ Java 层
catalystInstance.getJSModule(AppRegistry.class).runApplication(...)
│ 动态代理
│
▼
CatalystInstanceImpl.callFunction("AppRegistry", "runApplication", args)
│
══════════════════ Java → C++ 边界(JNI)══════════════════
│
▼ C++ 层
Instance::callJSFunction()
│
▼
NativeToJsBridge::callFunction()
│
▼
JSIExecutor::callFunction()
│ 调用 JS 的 callFunctionReturnFlushedQueue
│
══════════════════ C++ → JS 边界 ══════════════════
│
▼ JS 层
MessageQueue.callFunctionReturnFlushedQueue("AppRegistry", "runApplication", args)
│
▼
MessageQueue.__callFunction()
│ 从 _lazyCallableModules 找到 AppRegistry
│
▼
AppRegistry.runApplication("MyApp", params)
│
▼
renderApplication(<App />, initialProps) ← RN 应用开始渲染!
异步回调:Native 执行完后怎么把结果传回 JS?
当 JS 调用 Native 方法并传了回调函数时(如 NativeModule.method(callback)),回调的返回路径:
// Java 端:CallbackImpl.java
// Native 方法执行完毕,调用回调
@Override
public void invoke(Object... args) {
// 把结果和 callbackId 传回 C++
mJSInstance.invokeCallback(mCallbackId, Arguments.fromJavaArgs(args));
}
// C++ 层
void CatalystInstanceImpl::jniCallJSCallback(jint callbackId, NativeArray *arguments) {
instance_->callJSCallback(callbackId, arguments->consume());
}
void Instance::callJSCallback(uint64_t callbackId, folly::dynamic &¶ms) {
nativeToJsBridge_->invokeCallback((double)callbackId, std::move(params));
}
void JSIExecutor::invokeCallback(callbackId, arguments) {
// 调用 JS 层 MessageQueue 的 invokeCallbackAndReturnFlushedQueue
ret = invokeCallbackAndReturnFlushedQueue_->call(
*runtime_, callbackId, valueFromDynamic(*runtime_, arguments)
);
}
// JS 层:MessageQueue.js
class MessageQueue {
invokeCallbackAndReturnFlushedQueue(cbID, args) {
this.__invokeCallback(cbID, args)
}
__invokeCallback(cbID, args) {
// 通过 callbackId 找到之前存的回调函数
const callback = this._successCallbacks.get(cbID)
// 执行回调,把 Native 的结果传给 JS
callback(...args)
}
}
完整的异步回调流程:
JS: NativeModule.fetchData(callback)
→ enqueueNativeCall(moduleID, methodID, args)
→ 把 callback 存入 _successCallbacks[callbackId=42]
→ nativeFlushQueueImmediate → C++ → Java
→ Java 执行 fetchData,得到结果 {data: "hello"}
→ CallbackImpl.invoke({data: "hello"})
→ jniCallJSCallback(callbackId=42, {data: "hello"})
→ C++ → JSIExecutor.invokeCallback
→ JS MessageQueue.__invokeCallback(42, {data: "hello"})
→ _successCallbacks.get(42) → callback({data: "hello"})
→ 你的回调函数被执行 ✅
7.6 NativeModules:JS 调用原生能力
除了渲染指令,JS 还需要调用原生能力(相机、定位、存储等)。老架构通过 NativeModules 实现:
// JS 侧调用 Native 方法
import { NativeModules } from 'react-native'
// 同步获取模块引用(模块信息在启动时已注册)
const { NativeCommonModule } = NativeModules
// 调用 Native 方法(异步,通过 Bridge)
NativeCommonModule.methodXXX()
// 带回调的异步调用
NativeCommonModule.callNativeMethod('methodXXX', (result) => {
console.log(result)
})
Native 模块的注册流程:
Native 侧(以 Android 为例):
① 创建 NativeModule 类,继承 ReactContextBaseJavaModule
② 在 Package 中注册该模块
③ 应用启动时,所有 NativeModule 被收集到 NativeModuleRegistry
│
▼ 初始化 Bridge 时
│
C++ 侧:
④ NativeModule 信息传递到 C++ 层
⑤ 构建 moduleRegistry_ 对象
⑥ 挂载到 JS global 对象上:global.nativeModuleProxy
│
▼
JS 侧:
⑦ NativeModules.XXX 实际访问的是 global.nativeModuleProxy.XXX
⑧ 调用方法时,通过 Bridge 传递 moduleID + methodID 给 Native
// C++ 侧将 NativeModules 挂载到 JS global 上
void JSIExecutor::initializeRuntime() {
runtime_->global().setProperty(
*runtime_,
"nativeModuleProxy",
Object::createFromHostObject(
*runtime_,
std::make_shared<NativeModuleProxy>(nativeModules_)
)
);
}
// JS 侧访问 NativeModules.ReactNativeCommonModule 时
// 实际触发 nativeModuleProxy 的 get 方法:
Value get(Runtime &rt, const PropNameID &name) override {
auto nativeModules = weakNativeModules_.lock();
return nativeModules->getModule(rt, name); // 从注册表中查找模块
}
8. 新架构:JSI + Fabric + TurboModules
RN 0.68+ 引入了新架构,核心目标:干掉 Bridge,让 JS 和 Native 直接通信。
8.1 新架构三大组件
老架构:
JS ←──── Bridge(JSON 序列化,异步)────→ Native
新架构:
JS ←──── JSI(C++ 直接调用,同步)────→ C++ ←───→ Native
│ │
│ ┌────┴────┐
│ │ │
Fabric TurboModules
(新渲染系统) (新原生模块系统)
JSI(JavaScript Interface):
- JS 引擎的抽象层,让 JS 可以直接持有 C++ 对象的引用
- 不需要 JSON 序列化!JS 直接调用 C++ 方法
- 支持同步调用(不用等异步回调)
Fabric(新渲染系统):
- 替代老架构的 UIManager + Bridge 渲染流程
- Shadow Tree 在 C++ 侧构建(而不是 Native 侧)
- JS 通过 JSI 直接操作 Shadow Node
TurboModules(新原生模块系统):
- 替代老架构的 NativeModules
- 懒加载:用到时才初始化(老架构启动时全加载)
- 通过 JSI 直接调用,不经过 Bridge
8.2 JSI:JS 和 C++ 的直接对话
JSI 的核心能力:JS 可以直接持有 C++ 对象的引用,调用 C++ 方法,不需要序列化。
老架构(Bridge):
JS 对象 → JSON.stringify → 字符串 → JSON.parse → Native 对象
耗时:序列化 + 反序列化 + 异步等待
新架构(JSI):
JS 对象 → 直接引用 C++ HostObject → 调用 C++ 方法
耗时:一次函数调用(微秒级)
具体实现:
// C++ 侧:向 JS global 对象注入 nativeFabricUIManager
std::shared_ptr<UIManagerBinding> UIManagerBinding::getBinding(
jsi::Runtime &runtime) {
auto uiManagerModuleName = "nativeFabricUIManager";
auto uiManagerValue =
runtime.global().getProperty(runtime, uiManagerModuleName);
auto uiManagerObject = uiManagerValue.asObject(runtime);
return uiManagerObject.getHostObject<UIManagerBinding>(runtime);
}
// JS 侧:直接调用 C++ 方法(不经过 Bridge!)
var node = global.nativeFabricUIManager.createNode(
tag, // reactTag
viewName, // 'RCTView'
rootTag, // 根视图标识
props, // 属性
instanceHandle // Fiber 引用
)
// 这个调用是同步的!直接触发 C++ 的 createNode 方法
8.3 Fabric:新的渲染流程
Fabric 架构下,Shadow Tree 的构建从 Native 侧移到了 C++ 侧。
先澄清一个容易混淆的概念——“Native 侧构建”和”C++ 侧构建”说的不是一回事:
"Native 侧构建 Shadow Tree"(老架构):
指的是 Java(Android)/ OC(iOS)层构建 Shadow Tree
→ Android:UIManagerModule.java 中的 createView 方法创建 ReactShadowNode
→ iOS:RCTUIManager.m 中的 createView 方法创建 RCTShadowView
→ 每个平台各自实现一套,Shadow Node 的类型和实现都不同
→ 问题:iOS 和 Android 各写一遍,逻辑重复,且通过 Bridge 异步接收指令
"C++ 侧构建 Shadow Tree"(新架构 Fabric):
指的是用 C++ 统一构建 Shadow Tree,iOS 和 Android 共用同一份代码
→ C++:UIManager::createNode() 创建 ShadowNode
→ Yoga 布局引擎也在 C++ 层,直接计算布局
→ JS 通过 JSI 同步调用 C++ 方法,不经过 Bridge
→ 优势:跨平台共用、无序列化、同步调用
简单说:
老架构 → Java/OC 各自建各自的 Shadow Tree(通过 Bridge 异步接收指令)
新架构 → C++ 统一建一份 Shadow Tree(JS 通过 JSI 同步调用)
对比流程:
老架构渲染流程:
JS (Fiber Tree)
→ Bridge(JSON 序列化,异步)
→ Java/OC 层各自构建 Shadow Tree(平台相关代码)
→ Java/OC 层各自计算布局
→ Java/OC 层创建真实视图
新架构(Fabric)渲染流程:
JS (Fiber Tree)
→ JSI(直接调用,同步)
→ C++ 层统一构建 Shadow Tree(跨平台共用)
→ C++ 层计算布局(Yoga)
→ Java/OC 层创建真实视图(只负责最后一步:把布局结果变成真实 View)
Fabric 中 createNode 的实现:
// C++ 侧:UIManagerBinding 的 get 方法
jsi::Value UIManagerBinding::get() {
auto methodName = name.utf8(runtime);
if (methodName == "createNode") {
return jsi::Function::createFromHostFunction(
runtime, name, 5,
[uiManager](jsi::Runtime &runtime,
jsi::Value const &,
jsi::Value const *arguments,
size_t) noexcept -> jsi::Value {
auto eventTarget = eventTargetFromValue(runtime, arguments[4], arguments[0]);
// 直接在 C++ 侧创建 Shadow Node
return valueFromShadowNode(
runtime,
uiManager->createNode(
tagFromValue(arguments[0]), // tag
stringFromValue(runtime, arguments[1]), // viewName
surfaceIdFromValue(runtime, arguments[2]), // rootTag
RawProps(runtime, arguments[3]), // props
eventTarget // 事件目标
)
);
}
);
}
}
// UIManager 的 createNode 方法
ShadowNode::Shared UIManager::createNode() {
// 直接在 C++ 创建 Shadow Node
auto shadowNode = componentDescriptor.createShadowNode();
return shadowNode;
}
8.4 Fabric 中 Shadow Node 的不可变性
Fabric 架构中,Shadow Node 是不可变的(Immutable)。更新时不修改原节点,而是克隆:
老架构更新:
Shadow Node A (style: {color: 'red'})
→ 直接修改 → Shadow Node A (style: {color: 'blue'})
问题:多线程同时读写同一个节点,需要加锁
新架构(Fabric)更新:
Shadow Node A (style: {color: 'red'}) ← 旧的,不动
→ 克隆 → Shadow Node A' (style: {color: 'blue'}) ← 新的
→ 逐级向上克隆,生成新的 Shadow Tree
优势:无锁,线程安全,可以被 iOS 和 Android 同时读取
具体过程:
旧 Shadow Tree: 新 Shadow Tree:
Root Root'(克隆)
│ │
View View'(克隆)
╱ ╲ ╱ ╲
Text View Text' View(复用,没变)
│ │ │
"red" "hello" "blue" ← 只有这里变了
只克隆从变化节点到根节点的路径,其余节点复用。
这和 React 的 Fiber 双缓冲思想异曲同工。
8.5 新老架构对比总结
老架构 新架构
──────────────────────────────────────────────────────────────────
通信方式 Bridge(JSON 序列化,异步) JSI(C++ 直接调用,同步)
Shadow Tree Java/OC 层各自构建 C++ 层统一构建(Fabric,跨平台共用)
原生模块 NativeModules(启动时全加载) TurboModules(懒加载)
序列化开销 每次通信都要 JSON 序列化 无序列化,直接引用
同步调用 不支持 支持
线程安全 需要加锁 Shadow Node 不可变,无锁
跨平台复用 Shadow Tree 各平台独立 C++ Shadow Tree 跨平台共用
──────────────────────────────────────────────────────────────────
9. 事件处理与更新流程
9.1 RN 中的事件注册
在 Web 中,事件通过 addEventListener 注册到 DOM 上。在 RN 中,事件处理函数注册在 JS 侧,但触发来自 Native 侧:
// 你写的 RN 代码
function Demo() {
const [number, setNumber] = useState(0)
const handleClickAdd = () => {
setNumber(number + 1)
}
return (
<TouchableOpacity onPress={handleClickAdd}>
<View>
<Text>{number}</Text>
</View>
</TouchableOpacity>
)
}
事件注册的底层实现(Fabric 架构):
// RN 启动时,通过 nativeFabricUIManager 注册全局事件处理函数
var registerEventHandler = nativeFabricUIManager.registerEventHandler
if (registerEventHandler) {
// 注册 dispatchEvent 作为所有 Native 事件的入口
registerEventHandler(dispatchEvent)
}
这样 Native 侧感知到任何用户交互(触摸、滑动、键盘等),都会调用 dispatchEvent。
9.2 事件触发的完整流程
用户点击屏幕
① Native 侧捕获触摸事件
iOS: UIResponder 触摸链
Android: MotionEvent 分发
│
▼
② Native 找到被点击的 View 对应的 tag
│
▼ 通过 Bridge/JSI 传递给 JS
│
③ JS 侧 dispatchEvent 被调用
│
▼
④ 根据 tag 找到对应的 Fiber 节点
│
▼
⑤ 在 batchedUpdates 中执行事件处理函数
// dispatchEvent 的实现(简化)
function dispatchEvent(target, ...) {
// 找到事件对应的 Fiber 节点
var targetFiber = target
// 批量更新:合并同一事件中的多次 setState
batchedUpdates(function() {
// 构建事件对象,找到对应的事件处理函数,执行
runExtractedPluginEventsInBatch(
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget
)
})
}
9.3 批量更新:和 React Web 完全一样
// 批量更新的原理(Web 和 RN 共用)
var executionContext = NoContext
function batchedUpdates(fn) {
// 设置批量更新标记
executionContext |= BatchedContext
try {
// 执行事件处理函数
// 函数内的多次 setState 不会立即触发更新
return fn()
} finally {
// 重置标记
executionContext = prevExecutionContext
// 统一触发一次更新
}
}
// 例子:两次 setState 只触发一次更新
const handleClick = () => {
setNumber(1) // 不会立即更新
setName('Alien') // 不会立即更新
// handleClick 执行完毕后,batchedUpdates 统一触发一次更新
}
9.4 setState 触发更新的完整流程
用户点击 → handleClickAdd 执行 → setNumber(number + 1)
① setNumber 触发 dispatchAction(和 Web 一样)
│
▼
② 创建 update 对象,加入 Fiber 的更新队列
│
▼
③ scheduleUpdateOnFiber → ensureRootIsScheduled
│
▼
④ 进入 Render 阶段(和 Web 一样)
- beginWork:遍历 Fiber 树
- 执行 Demo 函数组件,useState 返回新值
- reconcileChildren:diff 子节点,打标记
│
▼
⑤ 进入 Commit 阶段(RN 独有的部分)
│
├─ 老架构:通过 Bridge 发送 updateView 指令
│ UIManager.updateView(tag, 'RCTText', { text: '1' })
│ → Native 收到指令 → 更新 TextView 的文字
│
└─ 新架构:通过 JSI 直接调用 C++ 方法
nativeFabricUIManager.cloneNodeWithNewProps(shadowNode, { text: '1' })
→ C++ 克隆 Shadow Node → Native 更新视图
│
▼
⑥ 屏幕上 0 变成 1 ✅
9.5 对比:Web 和 RN 的更新流程
React Web React Native
─────────────────────────────────────────────────────────────
事件来源 浏览器 DOM 事件 Native 触摸事件
事件传递 直接在 JS 中 Native → Bridge/JSI → JS
setState ✅ 相同 ✅ 相同
批量更新 ✅ 相同 ✅ 相同
Render 阶段 ✅ 相同 ✅ 相同
Commit 阶段 操作 DOM 发指令给 Native
视图更新 浏览器重绘 Native 主线程重绘
─────────────────────────────────────────────────────────────
10. 总结:一张图串联全流程
React Native 完整渲染流程
═══════════════════════════════════════════════════════════════
┌─────────────────────────────────────────────────────────────┐
│ 你写的代码 │
│ │
│ import { View, Text } from 'react-native' │
│ function App() { │
│ const [count, setCount] = useState(0) │
│ return <View><Text>{count}</Text></View> │
│ } │
└──────────────────────────┬──────────────────────────────────┘
│ Babel 编译
▼
┌─────────────────────────────────────────────────────────────┐
│ JS 线程(React 运行时) │
│ │
│ ① createElement → React Element (VDOM) │
│ ② Reconciler → Fiber Tree(和 Web 共用!) │
│ - beginWork:深度遍历,执行组件函数 │
│ - Hooks 在这里运行(useState, useEffect) │
│ - reconcileChildren:diff 打标记 │
│ ③ Commit 阶段 → 发送渲染指令 │
└──────────────────────────┬──────────────────────────────────┘
│
┌─────────────┴─────────────┐
│ │
老架构 Bridge 新架构 JSI
(JSON 序列化) (C++ 直接调用)
(异步) (同步)
│ │
▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ C++ 层 │
│ │
│ ④ 构建 Shadow Tree(布局树) │
│ - 老架构:Java/OC 层各自构建 │
│ - 新架构:C++ 层统一构建(Fabric),跨平台共用 │
│ ⑤ Yoga 引擎计算 Flexbox 布局(位置、大小) │
└──────────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Native 主线程 │
│ │
│ ⑥ 根据 Shadow Tree 创建/更新原生视图 │
│ iOS: UIView / UILabel / UIImageView │
│ Android: View / TextView / ImageView │
│ ⑦ 原生渲染引擎绘制到屏幕 │
└─────────────────────────────────────────────────────────────┘
═══════════════════════════════════════════════════════════════
核心要点:
1. React 核心(createElement、Hooks、Fiber、Reconciler)→ Web 和 RN 完全共用
2. 渲染器不同 → Web 用 react-dom 操作 DOM,RN 发指令给 Native
3. 中间多了 Shadow Tree → 用于跨平台布局计算(Yoga/Flexbox)
4. 通信方式 → 老架构用 Bridge(JSON 序列化,异步),新架构用 JSI(C++ 直接调用,同步)
5. 新架构三件套 → JSI(通信)+ Fabric(渲染)+ TurboModules(原生模块)