你好,我是蒋宏伟,今天我们来聊聊架构升级。
今年 2 月,超过 2000 人的调查报告——《State of React Native 2022》指出:有 6.3% 的开发者认为,React Native 版本升级是他们的痛点,位列所有问题之首。
是的,升级确实很痛。
正如上一讲中提到的,升级能够获得客观的性能收益,但成本太高了。当成本抵消收益时,理性的选择是放弃升级,即使无法享受性能收益,无法拥抱最新的生态,还可能会留下技术负担。
React Native 升级涉及许多方面,这些都是成本,包括 iOS、Android、JavaScript 三种语言的升级,多个团队的协同,所有项目的回归测试,以及因升级而放弃的业务研发的机会成本。只有在时机合适且收益能够覆盖业务的机会成本时,升级才是我们需要考虑的事项。
然而,一旦你确定要升级后,必须做好两方面的工作:一方面是规划好升级方案,减少升级对业务节奏的影响;另一方面是善用升级工具,加速项目升级。
那么这节课,我就来介绍下我对升级的整体思考。然后,我会重点介绍一个好用的升级工具——JSCodeshift,它可以帮助你自动批量升级项目。
升级方案
大概有以下3种升级方案:
- Expo 升级方案
- React Native Upgrade
- 将业务代码复制新版本上
下面,我会逐一介绍它们。
**第一种方案是 Expo。**Expo 提供了 eas-cli 和 expo 命令来帮助开发者进行升级。并且,每当发布一个新的 SDK 版本, Expo 都会提供相应的升级文档和建议。但是,由于 Expo 升级工具只能在 Expo 生态应用中使用,而我们主要使用原始的 React Native 项目,因此这里不做详细介绍。
**第二种方案是React Native Upgrade。**React Native 官方提供的升级工具只需要执行一行命令即可进行升级。
1 | $ npx react-native upgrade |
但当我尝试使用官方的 Upgrade 工具进行升级时,却失败了。就在上周,0.72 版本正式发布,我想着我们专栏的项目 react-native-classroom 也停留在 0.68 版本好久了,就准备将它从 0.68 升级到 0.72。但运行命令后,却发现报错了。
报错内容为 upgrade failed,并且还提示我有若干个 Android 文件升级冲突了。
于是,我查了一下 Upgrade 工具的原理,发现它无非是将 0.68 版本的模板代码和 0.72 版本的模板代码进行 diff,然后把 diff 后的结果应用到 react-native-classroom 项目上。其本质是在老项目中,应用 0.72 版本的模板代码。
**于是,我想到直接拷贝 0.72 模板代码的方案,这就是第三种方案。**我放弃了 Upgrade 工具,直接将 react-native-classroom 项目的所有 JavaScript 业务代码,包括 package.json 等文件,拷贝到新的 0.72 工程上,经过多次代码修改和安装构建后,我算是把 0.72 新架构版本的 react-native-classroom 给跑起来了。
依赖升级与代码修改
接下来,我说一下升级的细节和所踩的坑。
**第一类坑是第三方包的版本不匹配。**例如,在升级 React Navigation 后,我遇到了报错。在将 0.68 版本的依赖项拷贝到 0.72 版本的依赖项时,我遇到了几个类似的报错,报错信息不够明确,很难确定问题所在。
1 | ERROR Invariant Violation: requireNativeComponent: "RNSScreenStackHeaderConfig" was not found in the UIManager. |
针对这类问题,我在升级过程中采取了重装的方案,也就是将几乎所有依赖于 Native 的依赖项进行了版本升级和重新安装。通过这种方式,我成功解决了这类报错的问题。下面是重新安装的参考命令:
1 | // 先删除相关的依赖性,然后重新安装 |
**第二类坑是重装依赖后提示已经找不到本地依赖模块的问题。**例如,在我使用 Yarn 重装依赖重启应用时,Reanimated 报错没有正常初始化(doesn’t seem to be initialized)。
1 | ERROR Error: [Reanimated] The native part of Reanimated doesn't seem to be initialized. This could be caused by - not rebuilding the app after installing or upgrading Reanimated - trying to run Reanimated on an unsupported platform - running in a brownfield app without manually initializing the native library, js engine: hermes LOG Running "RN72" with {"rootTag":341} |
这类问题通常是由缓存导致的。只需清除 JavaScript 缓存并重新安装本地依赖和安装包,即可解决。以 Android 为例,步骤如下:
1 | // 重新安装 Android 依赖 |
**第三个坑是JavaScript代码的变动。**例如,Gesture 手势库在升级后要求必须在你的Root App组件外部包装一个 <GestureHandlerRootView>(类似于Redux中的 <Provider>)。这类改动相对容易,我把我遇到的相关报错和改动贴了出来,如下:
1 | ERROR Error: GestureDetector must be used as a descendant of GestureHandlerRootView. Otherwise the gestures will not be recognized |
开启新架构
上述三个坑踩完后,接着我开启了 React Native 的新架构。
要在Android上启用新架构,需先将 android/gradle.properties 文件中的 newArchEnabled 设置为true。然后重新安装依赖和构建Android包,你可以运行以下命令:./gradlew build --refresh-dependencies。
1 | // 修改 android/gradle.properties 文件中的变量 |
iOS 开启新架构也类似,重新安装依赖和 iOS 包,安装新架构依赖命令如下:
1 | # Run pod install with the flag: |
至此,咱们课程中用到的 react-native-classroom 升级 0.72 新架构算是完成了。
配置升级自动化
前面我们已经完成了单个项目的升级,但如果有二三十个项目需要升级,手动逐个升级的效率肯定很低。
那么,有哪些适合自动化的步骤呢?适合自动化的步骤包括:
- 依赖于Native的依赖项的配置
- JavaScript代码的升级
接下来,我们借助 Node.js 对配置实现自动升级,借助 JSCodeshift 对 JavaScript 代码实现自动化升级。
React Native 项目升级,主要涉及三个配置文件:
- package.json
- babel.config.js
- metro.config.js
**自动化升级的思路是:**先整理好需要升级的新配置,然后通过脚本对比升级需要更新的配置项,最后将更新后的配置项写入。
以 package.json 为例。假设你在同一个 App 中有 10 个项目要升级,只要你把其中 1 个项目升级成功,那么剩下的 9 个项目只要照葫芦画瓢即可。也就是说,我们只需将剩余 9 个项目的 package.json 中依赖 Native 的 dependencies 的相关配置和第 1 个项目对齐即可。
现在,我们已经将 react-native-classroom 项目升级完成了,我摘录了一部分的 dependencies 依赖的升级规则,如下:
1 | const navigationDeps = { |
要自动化地升级这部分配置,需要借助 Node.js 的力量。我们可以先读取 package.json 文件,再将最新的 dependencies(navigationDeps)插入到 package.json 文件中,最后将旧的 dependencies 删除即可。伪代码如下:
1 | // 伪代码 |
简单讲,自动升级配置的方案就是:先列举配置文件的升级规则,然后借助 Node.js 实现该升级规则。
代码升级自动化
完成自动化配置升级,接着我们继续完成代码的自动化升级。
JavaScript 代码本身不像配置文件,不借助一些工具读取和修改起来是非常麻烦的。
这里,我们使用正则修改和升级代码,但更好的方式是借助 AST 来升级它们。JSCodeshift 就是一款基于 AST 修改代码的工具。
与升级配置项类似,包括两步:
- 手动升级一个项目,列举升级规则
- 借助 JSCodeshift,将升级规则自动化
通过手动升级项目和官方文档,比如 React Native 各个版本的 Changelog,尽可能地找出需要改动的代码部分。
例如,React Native 在 0.60 版本移除了框架自带的 WebView 组件,开发者需要迁移到社区的 react-native-webview 组件。这就涉及到 WebView Import 规则的升级,升级规则示例如下:
1 | // 升级前 |
明确规则后,具体的实现咱们可以借助 ChatGPT 偷偷懒。我写的 Promot 如下:
1 | 我正在对 JavaScript 项目的代码进行升级,请你使用 JSCodeshift 写一段代码,对该项目下 case 目录的所有文件应用升级规则,直接进行代码替换。升级规则如下: |
经过多次沟通后,ChatGPT 给了我正确的答案。具体代码比较长,而且涉及到 AST 的概念,解释起来也比较麻烦。如果你感兴趣可以看看它给出的实现,但更重要的是掌握灵活使用 ChatGPT 的思路。
1 | const fs = require('fs'); |
代码升级自动化的关键是定义升级规则,然后借助 ChatGPT、JSCodeshift 这些工具,对其进行实现。
总结
升级 React Native 版本和新架构难免会遇到一些阻碍,而我们可以通过三个步骤逐步跨越这些障碍。
- 升级试点项目,并在试点过程中积累经验,包括收集所需升级的第三方依赖包和代码升级规则。
- 提前研发升级工具和 Native SDK,将第一步收集的升级规则,编程为 JSCodeshift 等自动化工具的代码,并提前准备好对应的新架构 Native SDK。
- 基建团队和业务团队协同升级。由于第二步的积累,业务团队能较快地将业务代码完成升级,最后统一发版上线。
完整代码我放在了GitHub上,供你参考,代码地址:0.72 新架构版本的 react-native-classroom
思考题
你在升级 React Native 版本过程中遇到过哪些困难,你又是怎么解决的?
期待你的分享,欢迎你把这节课分享给有需要的朋友,我们下节课再见!