React Native 0.76 新架构升级实录:我折腾了 11 天,Fabric 没让 App 快多少,但有个东西真的变了

🔑 关键词:React Native, 新架构, Fabric, TurboModules, JSI

📖 摘要:把公司 RN 项目从 0.72.6 升到 0.76.5 的完整记录。实测冷启动数据、踩过的 reanimated 和 Codegen 坑、自己写原生模块的改造取舍,以及一个不太受欢迎的观点:新架构对多数业务 App 来说不是性能银弹。

先说结论:性能没起飞,但有个东西变了

图片

上个月把手上这个 RN 项目从 0.72.6 干到了 0.76.5。项目不算大,四十多个页面,package.json 里躺着六十多个依赖,其中带原生代码的(就是有 android/ 和 ios/ 目录的那种)我数了数,23 个。iOS + Android 双端,Android minSdk 24。

为什么拖到现在才升?因为 0.72 那次我们刚从 0.68 爬出来,前后花了差不多三周,其中两周在跟一个早就停止维护的图片裁剪库纠缠。所以看到 0.76 默认开新架构的消息,我第一反应就是——再等等,等别人先踩。

结果等到今年年初,reanimated 3.x 和 screens 4.x 这几个主力库都稳定了,硬着头皮上。

结果怎么样呢。端到端冷启动,iPhone 13 和小米 13 各测 20 次取中位数,iOS 从 1.9s 左右降到 1.65s,Android 从 2.6s 降到 2.2s。但这提升主要来自 Hermes——它把 JS 的解析编译挪到了构建阶段,运行时省掉 parse 这一步。跟 Fabric 渲染器关系不大。

图片

社区里铺天盖地讲新架构渲染多快多快,我的实测是:长列表首屏上屏确实快了一点,几十毫秒量级吧,但你在真机上滑两下根本感知不到。用户体感最大的瓶颈永远是——网络请求什么时候回来、图片什么时候解码完。这两件事 Fabric 管不着。

新架构到底改了啥,别被那堆名词唬住

简单说,JSI 是地基,Fabric、TurboModules、Codegen 是上面盖的三栋楼。

老架构里 JS 和原生之间隔着一座 bridge,所有通信都得序列化成 JSON 字符串扔过去,还是异步批处理的。你在 JS 里调一个原生方法,它不会立刻返回,得等下一帧或者下一个 batch。JSI 干的事就是让 JS 引擎能直接持有 C++ 对象,调用变成同步的,中间没有序列化。TurboModules 是建在 JSI 上的原生模块,Codegen 读你的 TypeScript 类型定义,自动生成 C++ 胶水代码。Fabric 是新渲染器,Shadow Tree 挪到 C++ 里了,理论上支持并发渲染——不过那部分目前还没完全放开。

图片

那实际收益在哪?我认为被严重低估的一点是:同步调用能力。

举个我们项目里的真事。用户登录态存在 MMKV 里,老架构下要在 App 启动时 await 一次异步读取,读完才知道该渲染登录页还是首页。异步就意味着中间必然有一帧空白或者 loading。新架构下走 JSI 能同步读,第一次 render 就能拿到值,那一帧闪白没了。

这不是什么大功能,但它是「以前写不出来、现在能写」的差别。性能数字可以慢慢优化,能力边界只能靠架构改。这是我这次升级唯一觉得值的地方。

踩的坑,按恶心程度排个序

第一恶心的是 reanimated。我们原来锁的 2.14,新版必须上 3.x。2.x 在新架构下要开兼容模式,babel 配置还得单独加 plugin,加完之后跟 react-native-worklets 又打架。最后直接升到 3.16 才算干净。

图片

第二位是自己写的原生模块。我们有个统计上报的 module,几年前用 RCT_EXPORT_METHOD 那套写的。新架构下它还能跑,但走的是 interop 兼容层,等于性能红利一点没吃到。真想用上 TurboModule,得先写 spec 文件,把接口用 TS 类型描述一遍,再让 Codegen 生成 C++ 接口。代价是要动原生代码。我评估了一下,这个模块调用频率不高,就先用兼容层苟着,没改。

第三是 Android 构建。CMake 版本和 NDK 对不上,报了一堆 C++ 编译错误,最后锁 NDK 到 26.x 才过去。iOS 那边反而顺,就是 pod install 明显变慢,第一次跑了快十分钟。

还有个隐形坑:我们依赖的一个 UI 库两年没更新了,它内部包的 FlatList 在新架构下有偶发白屏。这种库最烦,你既不能改它,也不能删它。

你要是正准备升,先拿这几个维度量一下自己

图片

别急着翻官方升级文档,先数数你项目里有几个「带原生代码的第三方库」。这个数字基本决定了你的工作量。我的经验是大致这么个量级:10 个以内,周末加两天假能搞定;20 到 30 个,做好两到三周的心理准备;超过 40 个,我建议先别升,或者换个思路。

具体到单个库,我会看四样东西。最近一次 commit 时间,超过一年没动的基本可以判死刑。仓库里有没有 android/src/main/java 和 ios 目录,纯 JS 库的升级风险约等于零。README 里写没写支持新架构。issue 列表搜一下 new architecture,看看有没有人在骂。

再提一个很多人忽略的点:FlashList 和 FlatList 的取舍。我们信息流从 FlatList 换成 FlashList 之后,Android 端滑动掉帧明显少了,内存占用也降了一截。但它跑在新架构上会更稳,这算是个升不升的额外考量。

最后说个可能不太受欢迎的观点。如果你的 App 瓶颈主要在网络 IO 和图片解码上,而且也不需要同步调用原生能力,那新架构对你来说就是个纯成本项。升级换来的那点 TTI 提升用户感知不到,但团队要付出两到三周,还得背上一堆库不兼容的风险。别因为社区都在讲 Fabric,就觉得自己不升就落伍了。

图片

最后

这次升级前后大概花了 11 个工作日,真正写业务代码的时间不到两天,剩下全在跟构建系统和第三方库打架。

我现在对新架构的态度是:它不是性能银弹,是一次基础设施重构。改完之后,一些以前写起来别扭的东西变自然了;但你要指望它把 App 变快一倍,肯定失望。

还有个事得提醒一句。RN 官方迭代节奏挺快的,0.76 之后 0.77、0.78 陆续都来了,每个版本都会有些库掉队。我现在的做法是大版本先不跟,等三个月看社区反馈再决定。听着保守,但对小团队来说,稳定比新特性值钱多了。

🏷️ 标签: