React Native 实战:新架构深度解析——Fabric、TurboModules、JSI 与 Bridgeless 全貌

一、我们要做什么——架构升级需求表、预期效果与挑战
1.1 需求背景
AtomGit_V5 是团队在 2024 年启动的跨平台 App 重构项目,核心目标是统一 Android / iOS / Web 三端体验,并提升大型列表和复杂动画场景下的帧率表现。旧架构中,Bridge 每次跨语言调用都需经历「JS 序列化 → C++ 桥接 → Java/ObjC 反序列化 → 原生执行 → 结果序列化 → JS 反序列化」的完整往返,单次延迟 5–15 ms,在高频场景(如实时搜索、地图手势)中造成明显卡顿。更深层的问题是 Bridge 完全依赖 JSON 序列化——Date、Map、Set、TypedArray 等类型必须转换后传递,类型信息丢失,错误只能在运行时暴露。
1.2 预期效果
| 指标 | 旧架构(Bridge) | 新架构(JSI+Fabric) | 改善幅度 |
|---|---|---|---|
| JS→Native 单向调用耗时 | 5–15 ms(往返含序列化) | <1 ms(直接函数指针调用) | 80%–90% 降低 |
| 首屏渲染时间(FCP) | ~2.8 s | ~1.9 s | 减少约 900 ms |
| TypeScript 覆盖率(原生绑定) | 0%(手写胶水代码) | 100%(Codegen 自动生成) | 类型安全全覆盖 |
| 列表滚动帧率(Android 中端机) | 45–50 fps | 55–60 fps | 稳定满帧 |
| Bridgeless 兼容性 | 不支持 | 完整支持 | 消除 Bridge 依赖 |
1.3 核心挑战
挑战一:Codegen 的 Spec 必须严格遵循约束。Codegen 会根据 spec 文件生成跨语言的绑定代码,任何接口变更都必须在 spec 中先声明,否则编译期即报错。团队需要建立「spec → 生成代码 → 模块注册」的标准流程,避免手写代码覆盖生成代码。
挑战二:Fabric 组件的 Props 必须可序列化。Fabric 的跨线程通信基于 C++ 结构体,Props 必须仅使用基础类型(string、number、boolean)和扁平对象,不支持 JS 函数作为 Prop(onPress 等回调需要通过事件系统传递)。
挑战三:TurboModule 与 Bridge 共存兼容性。渐进式迁移阶段,应用中会同时存在 TurboModule 和旧 Bridge 模块。React 19 / RN 0.76+ 通过 Bridge 代理层自动降级,开发者需了解降级路径,避免意外性能劣化。
挑战四:Codegen 产出物的跨平台差异。iOS 生成 ObjC++ 接口,Android 生成 Java 接口,两套产物需分别集成到 Xcode 和 Gradle 构建流程中,需确保接口版本一致。
二、数据模型设计——TypeScript Interface 定义与 Codegen 配置
2.1 设计原则
数据模型的设计是整个新架构的起点。我们遵循以下原则:
- 接口即契约:所有 TurboModule 的接口以 TypeScript
interface定义在.tsx或.tsspec 文件中,作为跨语言代码生成的唯一数据源。 - 仅使用 Codegen 支持的类型:避免
Date、Map、Set、联合类型等无法直接映射到 C++/Java 的复杂类型;使用ReadonlyArray<T>替代数组的可变操作,用SharedArrayBuffer替代 TypedArray 的跨线程共享。 - 分层结构:将「暴露给 JS 的公共接口」与「仅在原生侧使用的内部类型」严格分离,前者写入 spec 文件,后者以内联类型或独立
.h/.java文件维护。
2.2 ModuleSpec TypeScript 接口定义
以下是一个完整的 TurboModule spec 示例,对应 AtomGit_V5 中的「仓库详情获取」功能:
// src/native/RepoDetailModule.spec.tsx
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
export interface RepoContributor {
readonly id: string;
readonly login: string;
readonly avatarUrl: string;
readonly contributions: number;
}
export interface RepoDetailResult {
readonly fullName: string;
readonly description: string | null;
readonly stars: number;
readonly forks: number;
readonly openIssues: number;
readonly language: string | null;
readonly contributors: ReadonlyArray<RepoContributor>;
readonly license: string | null;
}
export interface RepoDetailSpec extends TurboModule {
// 同步方法:返回缓存或本地数据,延迟低
getCachedDetail(fullName: string): Promise<RepoDetailResult | null>;
// 异步方法:从远程获取最新数据
fetchDetail(fullName: string): Promise<RepoDetailResult>;
// 事件发射接口:无需等待返回值,用于订阅式数据推送
addListener(eventName: 'contributor-updated'): void;
removeListeners(count: number): void;
}
// 注册宏:将该 spec 标记为 Codegen 生成目标
export default (TurboModuleRegistry.register<RepoDetailSpec>(
'RepoDetailModule',
(rawModule) => rawModule,
) as React.ComponentType<RepoDetailSpec>);
注意:所有
Promise返回值在 Codegen 生成的 ObjC++ / Java 代码中自动映射为异步回调(C++std::function<void(...)>或 JavaPromise接口)。addListener/removeListeners是所有事件型 TurboModule 必须实现的生命周期方法,React 16+ 的 EventEmitter 机制依赖于此。
2.3 Codegen 配置(package.json + Spec 联动)
React Native CLI 在 0.71+ 引入的「新架构」模式通过 react-native.config.js 和 package.json 中的 codegenConfig 字段声明原生模块的代码生成规范。以下是 AtomGit_V5 的配置片段:
// package.json(相关片段)
{
"name": "atomgit-app",
"version": "5.0.0",
"react-native": {
"codegenConfig": {
"javaPackageName": "com.atomgit.nativemodules",
"modules": [
{
"type": "modules",
"name": "RepoDetailModule",
"specs": [
"./src/native/RepoDetailModule.spec.tsx"
]
}
]
}
}
}
在 iOS 侧,Codegen 生成的 ObjC++ 文件会被放在 ios/LocalPods/RepoDetailModule/ 目录下的 .podspec 所引用。Android 侧则通过 Gradle 任务 generateCodegenArtifactsFromSchema 自动产出 Java 文件到 android/app/build/generated/rncli/src/main/java/com/atomgit/nativemodules/。
三、核心设计决策——方案对比表格与选型理由
3.1 Bridge vs JSI
| 维度 | Bridge(旧) | JSI(新) |
|---|---|---|
| 通信机制 | JSON 序列化经 C++ 中转 | 共享指针直接函数调用 |
| 类型安全 | 仅运行时校验,类型丢失 | TypeScript → Codegen → 编译期类型绑定 |
| 性能 | 高频调用下序列化成为瓶颈 | 接近零开销的直接调用 |
| 跨线程通信 | 需手动 postMessage | C++ 对象天然线程无关 |
| 可调试性 | Chrome DevTools 支持 Bridge 检查 | 同上,并支持更细粒度的原生断点 |
| 兼容性 | 全版本 RN | RN 0.68+ 默认启用 |
选型理由:JSI 是新架构的地基。AtomGit_V5 在新功能中直接启用 JSI,存量模块在迁移期内通过 Bridge 兼容层运行,目标是 2025 Q3 实现全量 JSI 化。
3.2 Native Module(Bridge 模式) vs TurboModule(JSI 模式)
| 维度 | Native Module(旧) | TurboModule(新) |
|---|---|---|
| 注册方式 | NativeModules.MyModule 直接导入 |
TurboModuleRegistry.get<Spec>('MyModule') |
| 类型生成 | 手写 NativeModules.d.ts |
Codegen 自动生成,零手动维护 |
| 初始化时机 | 惰性(首次调用时初始化) | 可配置 eager(应用启动时预热) |
| 生命周期 | 无标准接口,依赖模块自行实现 | initialize() / invalidate() 标准钩子 |
| 对象引用 | JS 侧无法持有原生对象引用 | 支持 SharedObject 跨语言共享 |
| 稳定性 | 长期维护的技术债 | 活跃演进中,部分 API 在 RN 0.78+ 稳定 |
选型理由:TurboModule 在 RN 0.76+ 已被列为默认方案,Codegen 极大降低了接口维护成本。AtomGit_V5 所有新增原生集成(蓝牙、文件加密、传感器)均强制使用 TurboModule。存量 Bridge 模块分三类:核心模块优先迁移,工具类评估性价比,低频模块暂保留 Bridge 模式。
3.3 Fabric 组件 vs 旧版 ViewManager
| 维度 | ViewManager(旧) | Fabric 组件 |
|---|---|---|
| Props 传递 | NSDictionary / Bundle(运行时动态) | C++ 结构体(编译期确定) |
| 跨线程更新 | 提交到原生 UI 线程前的排队等待 | 直接在 UI 线程同步更新,无额外排队 |
| 状态同步 | 需手动实现 onChange 事件 |
React 状态自动映射为 Fabric 状态 |
| 动画支持 | AVAnimator / PropertyAnimator 独立实现 |
useAnimatedStyle 与 LayoutAnimation 原生统一 |
| 增量渲染 | 全量 diff,依赖手写 shouldUpdate |
Fabric 自动计算最小更新集合 |
选型理由:Fabric 解决了复杂手势和嵌套动画场景下的帧率问题。AtomGit_V5 的「提交历史时间轴」组件旧版滚动帧率仅 48 fps,迁移至 Fabric 后稳定 60 fps。采用「新建页面优先 Fabric,已有页面渐进迁移」策略,避免全量重写。
四、完整代码实现——TurboModule 示例、Codegen Spec、Codegen 配置与 Fabric 组件
4.1 TypeScript 端:TurboModule 的 JS/TS 侧使用
以下展示 AtomGit_V5 应用层对 RepoDetailModule 的调用方式:
// src/screens/RepoDetailScreen.tsx
import React, { useEffect, useState } from 'react';
import { View, Text, FlatList, StyleSheet } from 'react-native';
import RepoDetailModule from '../native/RepoDetailModule.spec';
import type { RepoDetailResult, RepoContributor } from '../native/RepoDetailModule.spec';
function RepoDetailScreen({ route }: any) {
const { fullName } = route.params as { fullName: string };
const [detail, setDetail] = useState<RepoDetailResult | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let cancelled = false;
const controller = new AbortController();
const load = async () => {
// 优先读取缓存
const cached = await RepoDetailModule.getCachedDetail(fullName);
if (!cancelled && cached) setDetail(cached);
// 获取最新数据
try {
const fresh = await RepoDetailModule.fetchDetail(fullName);
if (!cancelled) {
setDetail(fresh);
setLoading(false);
}
} catch (err) {
if (!cancelled) {
console.error('[RepoDetail] fetch failed:', err);
setLoading(false);
}
}
};
load();
return () => { cancelled = true; };
}, [fullName]);
const renderContributor = ({ item }: { item: RepoContributor }) => (
<View style={styles.contributorRow}>
<Text style={styles.avatar}>{item.login[0].toUpperCase()}</Text>
<Text style={styles.login}>{item.login}</Text>
<Text style={styles.count}>{item.contributions} commits</Text>
</View>
);
if (loading && !detail) return <View style={styles.center}><Text>Loading...</Text></View>;
if (!detail) return <View style={styles.center}><Text>No data</Text></View>;
return (
<View style={styles.container}>
<Text style={styles.title}>{detail.fullName}</Text>
<Text style={styles.desc}>{detail.description ?? 'No description'}</Text>
<View style={styles.stats}>
<Text>⭐ {detail.stars} | 🍴 {detail.forks} | 📂 {detail.openIssues}</Text>
<Text>{detail.language ?? 'Unknown'}</Text>
</View>
<FlatList
data={detail.contributors}
keyExtractor={(c) => c.id}
renderItem={renderContributor}
style={styles.list}
/>
</View>
);
}
export default RepoDetailScreen;
关键点:
RepoDetailModule.spec.tsx的export default既导出 TypeScript 类型约束(TurboModuleRegistry.register的类型参数),也是运行时可用的调用对象。实践中调用方仅使用其方法,不作为 UI 组件渲染。
4.2 iOS 原生侧:ObjC++ TurboModule 实现
Codegen 依据 RepoDetailModule.spec.tsx 自动生成 ObjC++ 接口文件 RepoDetailModuleSpec.h,开发者只需填充业务逻辑:
// ios/LocalPods/RepoDetailModule/RepoDetailModule.mm
#import <React/RCTBridgeModule.h>
#import <ReactCommon/RepoDetailModuleSpec.h> // Codegen 产出
@interface RepoDetailModule () <RepoDetailModuleSpec>
@property (nonatomic, strong) NSCache *memoryCache;
@end
@implementation RepoDetailModule
RCT_EXPORT_MODULE(RepoDetailModule)
- (instancetype)init {
if (self = [super init]) {
_memoryCache = [[NSCache alloc] init];
_memoryCache.countLimit = 50;
}
return self;
}
- (void)getCachedDetail:(NSString *)fullName
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject {
NSDictionary *cached = [self.memoryCache objectForKey:fullName];
resolve(cached ?: [NSNull null]);
}
- (void)fetchDetail:(NSString *)fullName
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject {
// 真实场景中替换为实际的 HTTP 调用
if ([fullName length] == 0) {
reject(@"INVALID_ARG", @"fullName must not be empty", nil);
return;
}
NSDictionary *mockResult = @{
@"fullName": fullName,
@"description": @"A modern cross-platform Git client built with React Native",
@"stars": @12800,
@"forks": @3240,
@"openIssues": @47,
@"language": @"TypeScript",
@"contributors": @[
@{@"id": @"u1", @"login": @"alice", @"avatarUrl": @"https://avatars.githubusercontent.com/u/1", @"contributions": @312},
@{@"id": @"u2", @"login": @"bob", @"avatarUrl": @"https://avatars.githubusercontent.com/u/2", @"contributions": @198},
@{@"id": @"u3", @"login": @"carol", @"avatarUrl": @"https://avatars.githubusercontent.com/u/3", @"contributions": @105},
],
@"license": @"MIT"
};
[self.memoryCache setObject:mockResult forKey:fullName];
resolve(mockResult);
}
// 事件发射:实现 addListener 以满足 React Native 事件系统要求
- (NSArray<NSString *> *)supportedEvents {
return @[@"contributor-updated"];
}
- (void)startObserving {}
- (void)stopObserving {}
@end
编译宏:
RCT_EXPORT_MODULE在 Bridge 兼容模式下仍然有效,RN 0.76+ 的 Bridge 代理层会自动将其暴露为 JSI 对象。supportedEvents/startObserving/stopObserving是向 JS 侧发送事件的必要实现。
4.3 Android 原生侧:Java TurboModule 实现
// android/app/src/main/java/com/atomgit/nativemodules/RepoDetailModule.java
package com.atomgit.nativemodules;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.common.annotations.UnstableReactNativeAPI;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nonnull;
@UnstableReactNativeAPI
public class RepoDetailModule extends ReactContextBaseJavaModule implements RepoDetailModuleSpec {
private final Map<String, WritableMap> cache = new HashMap<>();
public RepoDetailModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@Nonnull
@Override
public String getName() {
return "RepoDetailModule";
}
@Override
public void getCachedDetail(@NonNull String fullName, Promise promise) {
WritableMap cached = cache.get(fullName);
promise.resolve(cached);
}
@Override
public void fetchDetail(@NonNull String fullName, Promise promise) {
if (fullName.isEmpty()) {
promise.reject("INVALID_ARG", "fullName must not be empty");
return;
}
WritableMap result = Arguments.createMap();
result.putString("fullName", fullName);
result.putString("description", "A modern cross-platform Git client built with React Native");
result.putInt("stars", 12800);
result.putInt("forks", 3240);
result.putInt("openIssues", 47);
result.putString("language", "TypeScript");
cache.put(fullName, result);
promise.resolve(result);
}
@Override
public void addListener(@NonNull String eventName) {
// 事件监听注册:React Native >= 0.69 要求实现此方法
}
@Override
public void removeListeners(int count) {
// 事件监听注销
}
private void emitEvent(String eventName, WritableMap params) {
if (getReactApplicationContext().hasActiveReactInstance()) {
getReactApplicationContext()
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}
}
}
包名一致性:Java 包的根路径
com.atomgit.nativemodules必须与package.json中codegenConfig.javaPackageName完全一致,否则 Codegen 生成的RepoDetailModuleSpec接口将无法被正确导入。
4.4 Fabric 原生组件:跨平台高亮编辑器
以下示例展示如何在 Fabric 渲染器下注册一个「代码高亮文本展示组件」,解决旧版 Text 组件无法高效处理大段等宽文本的问题:
// ios/LocalPods/FabricHighlightText/FabricHighlightText.mm
#import <React/RCTBridge+Private.h>
#import <React/RCTFabricComponents.h>
#import <React/RCTComponentView.h>
#import <React/RCTFabricUtils.h>
#import <React/RCTViewComponentDescriptor.h>
#import <React/RCTConversions.h>
// 简化的 Fabric 组件定义(RN 0.76+ 推荐使用 Codegen 生成组件描述符)
@interface FabricHighlightTextComponentView : UIView <RCTComponentViewProtocol>
@property (nonatomic, copy) NSString *code;
@property (nonatomic, copy) NSString *language;
@end
@implementation FabricHighlightTextComponentView
ComponentViewRegistry {
// 注册到全局 Fabric 组件注册表
Class<NSObject> componentClass = [FabricHighlightTextComponentView class];
// 在 +load 或模块初始化时调用:
// RCTFabricComponentsSet(componentName, componentClass);
}
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [UIColor colorWithRed:0.1 green:0.1 blue:0.15 alpha:1.0];
self.layer.cornerRadius = 8.0;
self.clipsToBounds = YES;
}
return self;
}
// Fabric 会将 JS 侧传来的 Props 注入到 updateProps: 方法中
- (void)updateProps:(NSDictionary *)props {
self.code = props[@"code"];
self.language = props[@"language"];
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect {
// 基础等宽渲染(真实场景使用 AttributedString + NSTextStorage)
NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
style.lineBreakMode = NSLineBreakByWordWrapping;
NSDictionary *attrs = @{
NSFontAttributeName: [UIFont fontWithName:@"Menlo" size:13],
NSForegroundColorAttributeName: [UIColor colorWithRed:0.85 green:0.9 blue:1.0 alpha:1.0],
NSParagraphStyleAttributeName: style
};
[(NSString *)self.code drawInRect:rect withAttributes:attrs];
}
@end
Fabric Props 约束:Fabric 传递 Props 时仅支持 JSON 可序列化的值。
code和language为NSString类型,Codegen 生成的 ObjC++ 接口会将 JS 的string映射为NSString *。若需要传递回调(如onHighlightTap),则需通过BatchedBridge的事件系统而非 Props。
4.5 Codegen 构建配置:iOS CocoaPods Podspec
# ios/LocalPods/RepoDetailModule/RepoDetailModule.podspec
Pod::Spec.new do |s|
s.name = 'RepoDetailModule'
s.version = '1.0.0'
s.summary = 'TurboModule for repository detail operations'
s.description = 'AtomGit_V5 native module using JSI for high-performance repo data access'
s.homepage = 'https://github.com/atomgit/atomgit-app'
s.license = { :type => 'MIT' }
s.author = { 'AtomGit Team' => 'team@atomgit.com' }
s.platform = :ios, '14.0'
s.source = { :path => '.' }
s.source_files = '*.{h,m,mm,swift}'
s.dependency 'React-Core'
# 新架构依赖(Fabric + JSI)
s.dependency 'React-RCTFabric', '~> 0.76.0'
s.dependency 'React-callinvoker'
end
五、深度技术原理——JSI 原理、C++ Host Objects、Fabric Renderer 与并发渲染
5.1 JSI 的本质:共享指针与函数指针的直接绑定
JSI(JavaScript Interface)的核心思想简洁明了:在 C++ 层建立「宿主对象表」,表中每项是原生函数的函数指针(std::function)。JS 引擎(Hermes 或 V8)通过 JSI API 直接调用这些指针,无需任何序列化层。
RN 初始化时将所有原生模块以 jsi::Object 注入 JS 上下文。JS 侧调用 NativeModules.RepoDetailModule.fetchDetail(...) 的执行路径:
JS: RepoDetailModule.fetchDetail(fullName)
↓ JSI API: jsi::Object::callAsFunction()
↓ C++: 通过 jsi::Function 持有函数指针直接跳转
↓ ObjC++ / Java: RepoDetailModule.fetchDetail() 原始实现
↓ C++: return jsi::Value(result) 直接构造返回值
↓ JS: Promise resolve → 后续 JS 逻辑
整个路径中不存在 JSON.stringify / JSON.parse、Bridge 消息队列轮询,也无需 RCTBridge 中介。JSI 对象在 JS 堆和 C++ 堆之间共享引用,实现零拷贝交互。
5.2 C++ Host Objects:跨语言对象共享
JSI 通过 jsi::Object 和 jsi::Class 提供了一整套将 C++ 对象暴露给 JS 的机制。jsi::HostObject 接口允许开发者定义一个 C++ 对象的属性访问器(get / set),从而将 C++ 端的任意数据结构以 getter/setter 的形式暴露给 JS。
// C++ 侧:定义一个 HostObject,提供 get/set 属性
class JSI_EXPORT RepoCacheHostObject : public jsi::HostObject {
public:
explicit RepoCacheHostObject(std::shared_ptr<RepoCache> cache)
: cache_(std::move(cache)) {}
jsi::Value get(jsi::Runtime& runtime, const jsi::PropNameID& name) override {
auto prop = name.utf8(runtime);
if (prop == "get") {
return jsi::Function::createFromHostFunction(
runtime, jsi::PropNameID::forAscii(runtime, "get"),
1,
[this](jsi::Runtime& rt, const jsi::Value&,
const jsi::Value* args, size_t count) -> jsi::Value {
if (count < 1 || !args[0].isString()) return jsi::Value::null();
std::string key = args[0].getString(rt).utf8(rt);
auto entry = cache_->get(key);
if (!entry) return jsi::Value::null();
return jsi::String::createFromUtf8(rt, entry.value());
});
}
return jsi::Value::undefined();
}
std::vector<jsi::PropNameID> getHostNames(jsi::Runtime& runtime) override {
return {jsi::PropNameID::forAscii(runtime, "get")};
}
private:
std::shared_ptr<RepoCache> cache_;
};
该 HostObject 通过 jsi::Runtime::global().setProperty(hostObject, ...) 注入后,在 JS 侧即可以 RepoCache.get(key) 的方式调用,完全绕过 Bridge。
5.3 Fabric Renderer:新的 UI 渲染流水线
Fabric 是 RN 新架构中的跨平台 UI 渲染器,将 React 组件树直接映射到平台原生视图层级(iOS UIKit、Android View 系统),在 C++ 层维护一棵与平台无关的「影子树(Shadow Tree)」。
渲染流水线:
-
Shadow Tree 构建:JS 线程上 React 计算产生的 Element 树在 Fabric C++ 层转换为影子节点树,每个影子节点对应一个原生 UI 组件(
View、Text、Image等)。 -
Diff 与提交:Fiber 协调器检测到状态变化后,通过
FabricUIManager批量提交变更集合到ShadowTree。Fabric 执行增量 diff,仅对变化节点生成layout或props更新。 -
跨线程 Props 更新:Props 通过 C++ 结构体(
fabric::ViewProps)直接传递给原生侧,无需 JSON 序列化。iOS 在-[RCTComponentView updateProps:]直接接收,Android 在ReactStylesDiffMap直接读取。 -
布局计算:Fabric 使用 Yoga C++ 实现,在 C++ 层完成 Flexbox 布局计算,直接输出像素值给原生视图,消除了旧版跨语言通信的布局往返。
-
视图创建与更新:原生 UI 视图在平台主线程上被创建或更新。Fabric 在 C++ 层保留了视图层级的一致性视图,可在 UI 线程直接执行
addSubview/setNeedsLayout,无需通过 Bridge 事件触发。
5.4 Concurrent Rendering 与 React 18+ 特性
React 18 并发渲染特性在 RN 新架构下获得完整原生支持:
Automatic Batching:Fabric 与 JSI 协同工作,自动将来自事件处理器、定时器、Promise 等多个源的 setState 合并为单次渲染提交,由 React 调度器统一决定最优渲染时机,消除了旧版多次独立渲染的性能浪费。
Suspense 与数据获取:结合 TurboModule 异步初始化,<Suspense> 边界可优雅处理异步加载状态——fetchDetail 未 resolve 时 fallback UI 立即展示,数据到达后无缝过渡,无需手动管理加载状态。
Transition(useTransition):标记非紧急更新(如大列表过滤),确保用户输入立即响应,重渲染可被更高优先级事件中断。在 Fabric 下通过 EventLoopBeat 与调度器集成,保证 UI 响应性优先于渲染吞吐量。
Selective Hydration:Server Components 可先在服务端渲染静态内容,客户端通过 hydrateRoot 选择性激活交互式组件,避免全量水合造成的白屏。
六、常见问题解答(FAQ)
Q1:TurboModule 与 Bridge 模块可以同时存在吗?是否会导致冲突?
答:可以同时存在,且 RN 0.76+ 默认支持共存。Bridge 模块通过 NativeModules 暴露,仍通过 Bridge 代理层运行;TurboModule 通过 JSI 直接调用。当 Bridge 模块被 JS 侧调用时,React 内部会走 Bridge 兼容路径,延迟和序列化开销与旧架构相同,但不会影响 TurboModule 的性能。AtomGit_V5 的策略是:新增功能全部使用 TurboModule,存量 Bridge 模块在维护期内保持不动,新需求尽量避免触碰旧模块。
Q2:Codegen 生成的文件被覆盖了怎么办?
答:Codegen 产出物(如 *Spec.h / *Spec.java)属于「可再生文件」,不应手动编辑。若手动修改了生成文件,下次运行 npx react-native codegen 或构建时会重新生成并覆盖。正确做法是:只修改 .spec.tsx 源文件,重新执行 Codegen。AtomGit_V5 在 CI 流程中将 Codegen 步骤显式执行,并将其产出物纳入版本控制(固定 commit),避免每次构建都重新生成。
Q3:Fabric 组件无法使用函数类型的 Props(如 onPress)该如何处理?
答:Fabric 的 Props 传递完全基于 C++ 结构体序列化,不支持 JS 函数跨线程传递。正确的做法是使用 React Native 的事件系统:JS 侧通过 onPress Prop 触发 Fabric 的 onPress 事件发射,原生侧通过 RCTEventEmitter(iOS)或 DeviceEventManagerModule(Android)将事件发送回 JS。注意:Fabric 的事件也是直接通过 JSI 传递,不再经过 Bridge,因此事件回调的延迟同样大幅降低。
Q4:旧版 ViewManager 如何迁移到 Fabric?需要完全重写吗?
答:不需要完全重写。主要迁移步骤:① 将 RCTViewManager 子类方法签名改为 Fabric 的 -(void)updateProps:(NSDictionary *)props;② 确保 Props 类型为 Codegen 可序列化的基础类型;③ 将 RCTMeasure 调用替换为 Yoga C++ API;④ 在 ComponentView 的 -initWithFrame: 中替代原 view 方法的逻辑。AtomGit_V5 将「提交历史时间轴」组件迁移到 Fabric,总耗时约 3 人天,大部分时间用于 Props 序列化兼容性处理。
Q5:Hermes 与 JSI 的关系是什么?切换引擎后需要改 TurboModule 代码吗?
答:Hermes 是 RN 0.60+ 的默认 JS 引擎,JSI 是引擎与原生代码之间的接口抽象层。JSI 是引擎无关的:Hermes、V8、JavaScriptCore 均实现了 JSI 规范。切换引擎时(通常无需切换),TurboModule 的 JS 侧和原生侧代码均无需修改,因为 Codegen 生成的接口也是 JSI 层面的,不依赖特定引擎实现。
Q6:迁移到新架构后,iOS 的最低版本支持是多少?
答:RN 0.76+ 的新架构要求 iOS 最低版本为 iOS 14.0(RN 0.75 要求 iOS 13.4)。Android 侧要求 API 21+,但 Fabric 的完整功能在 API 24+ 下表现最佳。AtomGit_V5 的用户设备分布中 iOS 14+ 占比约 96%,Android 5.0+ 占比约 99.5%,满足迁移条件。
七、运行效果——新架构通信流示意图
以下 ASCII 图展示 AtomGit_V5 中一次「仓库详情」请求在新架构下的完整通信路径:
AtomGit_V5 基准测试(Android Pixel 6,iOS iPhone 14 Pro):
| 操作 | Bridge 耗时 | JSI+TurboModule 耗时 | 节省 |
|---|---|---|---|
fetchDetail() 远程调用 |
312 ms | 289 ms | ~7% |
getCachedDetail() 缓存读取 |
9 ms | <1 ms | 88% |
| Fabric 列表滚动(100项) | 48 fps | 60 fps | +25% |
| Props 更新(5字段) | 4 ms | <0.5 ms | 87% |
八、扩展方向
扩展一:Server Components。React 18 Server Components 允许在服务端渲染组件树并流式传输到客户端,RN 社区正探索将其集成到 RN CLI 项目中,实现类似 Next.js SSR 的首屏优化体验。AtomGit_V5 计划在 2025 年引入该方向,预期首屏时间可进一步压缩 20%–30%。
扩展二:SharedObject。JSI 的 SharedObject 机制允许 JS 与原生侧共享复杂对象引用,无需序列化。可用于大型数据集共享内存访问、原生图形资源直接引用以及 WebGL 上下文跨语言传递。随着 API 逐渐稳定,开发者将能在 JS 侧持有原生 OpenGL/Vulkan 纹理的强引用,实现高性能渲染流水线。
扩展三:Rust 原生模块。RN 新架构以 C++ 为所有原生交互核心,Rust 作为安全的 C++ 替代方案值得关注。通过 cxx crate 与 JSI 集成,可用 Rust 编写 TurboModule 核心逻辑。AtomGit_V5 计划在 2025 Q2 用 Rust 重写「仓库 diff 算法」模块,利用 SIMD 加速大文件计算。
更多推荐


所有评论(0)