你好,我是蒋宏伟。
有同学后台留言说:“我把公司应用升级到了 0.71.8 版本,开启新架构后,一些组件都乱了,折腾了三天最终无奈退回到老架构了。”
这个问题还挺典型的,今天我们就来聊聊到底应该怎么升级?分享一些经验。
在上节课中,我把我们的教学项目 React-Native-Classroom 项目,从 0.68 升级到了 0.72 的新架构,迁移过程十分顺利。这是因为 React-Native-Classroom 项目没有使用自定义模块或组件,它使用的都是官方的或成熟的社区方案,这些方案都对新老架构进行了兼容。所以,无需额外的 Native 代码升级工作。
但是,新旧架构的自定义模块或组件研发方式完全不同。原来那些自己维护的自定义模块或组件,要完全升级成 Turbo Modules 和 Fabric Components 是需要大量的改造工作的。
对于业务而言,研发效率是第一位的。那如何升级新架构,才能降低升级成本呢?
先说结论:升级新架构时不动旧的业务代码,是一个好策略。
老代码是否兼容?
那首先,我们就来测试,老代码不做任何修改,能否直接在新架构上运行?
我参考了官方文档中的老架构部分,分别创建了一个老模块和一个老组件。
老模块是自定义的简化版存储模块 StorageModule。它有两个方法,一个是利用键值对的形式存储字符串,另一个是使用键去读取存储在 Native 端的值。如果存储(SET ITEM)和读取(GET ITEM)成功,那么就说明这类老模块代码能在新架构中直接使用。
其 JS 的部分代码如下:
1 | import {NativeModules} from 'react-native'; |
老组件是一个自定义视图 CustomView,它只有简单的设置背景颜色的功能。如果颜色设置成功,那么就说明这类老组件代码在新架构中能够直接使用。
其 JS 部分的代码如下:
1 | import {requireNativeComponent} from 'react-native'; |
这些原本运行在老架构上的代码,在开启新架构后,运行的结果截图如下:

可以看到,老的自定义模块 StorageModule 正常运行。它能够从 Native 侧正确地存储和读取 “Hello, World!” 这段文字。
然而,老的自定义组件报错了。报错内容以文字形式显示在界面上,提示我 CustomView 老组件不兼容 Fabric,无法在新架构中运行。
从上述简单的演示中,可以推测官方默认对老架构的自定义模块代码做了兼容,但并未对老架构的自定义组件代码进行直接的兼容。
老组件兼容方案
为了兼容老架构的自定义组件,官方提供了一种手动开启兼容模式的方案——New Renderer Interop Layer。
显然,如果历史代码众多,底层架构升级需要大规模改动业务研发代码,这对业务来说无疑是一个巨大的负担。在 Facebook 内部,许多业务都使用 React Native,因此基建和业务研发团队肯定也经过了几轮讨论。兼容方案显然能够降低业务团队的升级成本。
所谓的 New Renderer Interop Layer 就是通俗理解的组件兼容层,它起源于 Facebook 内部,在内部使用了一段时间后,于 0.72 版本进行了开源。
开启组件兼容层的方法有三步。
首先,在根目录创建 react-native.config.js 文件。
1 | $ touch react-native.config.js |
然后,分别列举 iOS 和 Android 需要兼容的组件,将这些组件填写在 unstable_reactLegacyComponentNames 字段中。在我的代码中,旧架构的组件名字是 CustomView。因此,文件内容如下:
1 | module.exports = { |
最后一步,重新构建和启动 App。
1 | $ yarn android |
完成上述三步后,在重新启动的 App 中,你就能看到旧架构自定义组件 CustomView 能够正常展示 color=“red” 的红色了。

但是,这个兼容方案并不完美。
因为,官方并不打算 100% 支持所有的旧组件代码。官方给出的理由是,完全兼容可能会阻碍新架构性能的最大化展现。因此,官方希望社区的所有老架构的自定义组件都能迁移到新架构。
实际上,绝大部分功能都能得到兼容,包括:
- Props
- Events
- Native View Commands(即使用 UIManager.dispatchViewManager)
- Native Methods(如 setNativeProps 和 measure* 函数)
- 向 JS 导出的常量
已知不能兼容的包括:
- Concurrent Features(如 startTransition)
从现有的兼容方案来看,组件中常用的功能兼容层都已经支持,而 Concurrent Features 在老架构中就不支持,迁移后仍然不支持也是可以理解的。
由此可见,绝大部分老架构的组件代码都可以通过兼容层来适配新架构。
完全迁移到新架构
然而,如果我们继续在新架构上运行老代码,就无法享受到新架构带来的性能提升。在[第34讲]中,我们已经讨论过,新架构的组件渲染在 Android 上可以提高 0%~8% 的性能,在 iOS 上则可以提升 13%~39% 的性能。
因此,对于有时间和资源进行代码迁移的团队,或者正在开发新组件的团队,我仍然建议采用新架构的开发方案。
在[第 22 讲]中,我们介绍过新架构的自定义组件,但那时候还没有 CodeGen 自动化升级工具,所有的 Turbo Modules 和 Fabric Component 都是我们手动创建的。现在,我们可以利用 CodeGen 工具帮助我们自动化生成很多模板代码。
在此,我将以在 Android 上开发 CustomView 组件为例,来介绍 Fabric 自定义组件的研发流程。这个自定义组件只有一个功能,那就是改变其背景颜色。我在 GitHub 上发布了这个组件完整的新架构代码和老架构代码。
整体开发流程包含三个步骤:
- 定义组件(JS):你需要把对组件的设计思想编写成 TypeScript 的 Interface,并将其定义成一个 npm 包,以便后续使用。
- 实现组件(Native):利用 CodeGen 自动化工具,将 TypeScript 的 Interface 生成为 Native 的 Interface,并通过一些模板代码将 JS 和 Native 连接起来。
- 使用组件(JS):在 JS 中使用由 Native 实现的代码。
在开始开发之前,你需要创建一个用于开发 CustomView 的文件夹。
在这里,我直接在项目的根目录下创建了一个名为 RTNCustomView 的文件夹。文件夹的名称以 RTN 开头是为了提高其辨识度,因为 RTN 是 React Native 的缩写。
这个文件夹包含三个部分,具体如下:
1 | . |
其中,android 和 ios 文件夹用于存放该组件的客户端代码,而 js 文件夹用于存放 JS/TS 代码。
定义组件
定义组件的步骤主要包括两个部分:
- RTNCustomViewNativeComponent.tsx:这是自定义组件的 TypeScript Interface。
- package.json:这是自定义组件 npm 包的配置文件。
首先,在 js 目录中,我创建了一个名为 RTNCustomViewNativeComponent.tsx 的文件。这个文件以 NativeComponent 结尾,这是官方约定的规则,因为后续 CodeGen 需要根据 NativeComponent 结尾的标识找到 TypeScript 的规范文件,然后进行代码生成(code generate)。
RTNCustomViewNativeComponent.tsx 文件的内容如下:
1 | import type {ViewProps, HostComponent} from 'react-native'; |
在这里,我们约定这个组件只能接受一个自定义参数 background,这个参数的类型是 string。
此外,在 RTNCustomView 目录下,我创建了一个名为 package.json 的文件。这个文件的内容如下:
1 | { |
在这里,需要特别关注的是 codegenConfig 的配置,包括包名 name、类型 type 以及规范的目录 jsSrcsDir。
**有一些约定俗成的规则:**包名必须以 Specs 结尾;组件类型应填写 component,模块类型应填写 modules,这里填写的是 component;规范的目录就是我们之前创建的 js 目录。
实现组件
Native 这部分的内容相对较多,以 Android 为例,包括五个部分:
- build.gradle:这是 Android 模块的构建配置。
- CodeGen:这是用于生成链接 JS/Java/C++ 的模板代码。
- CustomView.java:这是新的原生组件。
- CustomViewManager.java:这是原生组件的管理器。
- CustomViewPackage.java:这个部分负责将原生组件暴露给 React Native。
整个结构如下:
1 | RTNCenteredText |
整个流程开始于配置 Android 的构建脚本,然后使用 CodeGen 自动化工具生成胶水代码,把 JS/Java/C++ 连接起来,接着定义新的 Java 自定义原生组件,再创建一个管理这个组件的类,最后在 React Native 组件列表中注册这个组件,使其能够被 JavaScript 代码访问。
下面我们详细讲解下流程。
第一步:配置 build.gradle
build.gradle 是 Android 构建系统用来定义构建规则的文件。在这个配置文件中,绝大部分都是模板代码,你只需要复制和粘贴就可以了,需要修改的只是自定义组件的名字。在这里,我使用的是 namespace "com.rtncustomview"。完整的配置如下:
1 | buildscript { |
第二步:运行 CodeGen
CodeGen 是一个自动代码生成工具,它可以根据之前创建的 RTNCustomViewNativeComponent.tsx 规范文件,自动生成连接 JS、Java 和 C++ 的模板代码。这样,你就可以在 JS 中方便地调用原生方法,同时也避免了手动编写这些模板代码的繁琐工作。
在运行 CodeGen 之前,你需要先将 RTNCustomView 组件添加到 package.json 文件中,然后通过 android 命令来生成模板代码。命令如下:
1 | // 在 RN 项目根目录运行 |
第三步:创建 CustomView.java
接下来,我们要创建一个新的原生组件,名为 CustomView.java。这个 Java 类将扩展一个 Android 原生视图类(例如 TextView),并定义所需的属性和行为。
在这个类中,你可以定义你需要的方法和属性,这样就可以在 JS 中直接访问这些方法和属性。同时,你还需要定义一些用来设置和获取这些属性的方法,这样,React Native 就能通过这些方法与原生组件进行交互。
完整代码如下:
1 | package com.rtncustomview; |
其核心是 public void setBackground(String color) 方法,它对应着 RTNCustomViewNativeComponent.tsx 定义的 background 属性,它是真正使 android.view.View 视图背景色设置生效的代码。
第四步:创建 CustomViewManager.java
每个原生组件都需要一个管理器类,CustomViewManager.java 就是 CustomView.java 的管理类。管理器类通常需要扩展 SimpleViewManager,并实现必要的方法,例如 createViewInstance() 方法用于创建原生组件的实例。
对于 React Native 自定义组件,我们还需要使用 @ReactProp 注解来标记可以从 JS 端设置的属性,这样 React Native 就可以通过这些方法来更新原生组件的属性了。
其完整代码如下:
1 | package com.rtncustomview; |
第五步:升级 CustomViewPackage.java
在创建了 CustomViewManager.java 之后,我们需要创建一个 CustomViewPackage.java 来包含我们的 ViewManager。在 createViewManagers 方法中,我们将添加我们刚刚创建的 CustomViewManager。
完整代码如下:
1 | package com.rtncustomview; |
使用组件
在组件使用方面,回到 JS 代码中,使用在客户端实现的组件。
首先运行 yarn add ./RTNCustomView 命令,重新添加 RTNCustomView,这样可以刷新我们实现组件的代码。
然后编写一段测试代码,代码如下:
1 | import React from 'react'; |
**RTNCustomView 的使用方式非常类似于其他原生组件。**你可以向它传递各种属性,例如 background 和 style。在这个例子中,我们设置了 background 为 "blue",并且设置了它的宽度和高度。
这里的 style 属性设置了视图的宽度和高度。请注意,尽管在 CustomView.java 文件中我们只提供了 setBackground 方法用于修改颜色,但 React Native 的视图管理器为所有的原生视图组件默认提供了对 style 属性的支持。
完整代码,我放在 Github 上了。
最后,重新构建 Android 应用,你就能看到代码生效了。

总结
这节课我介绍了两种升级新架构的策略。
一种策略是老代码兼容方案,该方案需要开启组件兼容层才能使用,而且可能会有部分性能损耗。另一种策略是完全迁移到新架构的方案,课程中我以新建 Android 组件为例,介绍了迁移新架构的策略。
可以看到,在新架构模式下,自定义组件的创建方式与老架构有很大的不同,新架构是以包为单元将各端统一在一起来进行开发的,老架构代码都在自己的 JS/Android/iOS 仓库中。这意味着,将老代码改为新架构的写法可能会有巨大的迁移成本。
因此,我的建议是,新的组件或模块使用新架构,而旧的代码保持不变,使用兼容模式。这种升级策略成本会低很多。
那么到这节课,我们的动态更新专栏也要和大家说再见了,感谢你过去一年的陪伴。因为有你,所以一同成长。我们有缘,再会!