React Native 做为一款跨端框架,有一个最让人头疼的问题,那就是版本更新。尤为是遇到大版本更新,JavaScript、iOS 和 Android 三端的配置构建文件都有很是大的变更,有时候三者的配置文件又互相耦合在一块儿,每每牵一发而动全身。javascript
本文假定 React Native 升级的主导者是前端同窗,比较熟悉 javaScript 为主的一套前端构建流程。若是有条件,升级时强烈建议拉上 iOS 和 Android 开发,对于一些琐碎的升级细节,当面沟通远比搜索引擎高效。html
提示:由于每次修改和新增内容都会隐藏文章从新审核,建议阅读博客原文得到最佳阅读体验
👉 阅读博客原文前端
以为文章对你有用的话必定要记得点赞哦 🌟,谢谢你,这对我来讲真的很重要!java
这部分知识我认为是最重要的,毕竟版本更新是永恒的,操做流程倒是不变的。node
详细介绍各端构建工具前,咱们抛开各类技术细节,从整个项目的生命周期出发,看看大部分产品是怎么作技术规划的:react
理清一个技术产品的生命周期后,你就会对平常开发中配置文件有了总体的认知:那些又臭又长的配置项,乱七八糟的兼容写法,毫无美感的 DSL,最神奇的是这些七拼八凑的东西还能把项目跑起来,Build 成功的那一刻你必定会对这种人类奇迹发出由衷的敬佩之情——原来这就叫专业啊!android
收一收澎湃的情绪,牢记上面的指导经验,咱们下面开始讨论技术细节。webpack
前端工程化一直是前端里面的热点,虽然一直很热,可是具体实现仍是一团糟。我的认为缘由主要有两点,一个是前端构建从无到有,相对而言基础薄弱;一个是社区推进,百花齐放的同时又没有统一标准。就拿如今前端的主要配置文件来讲:ios
package.json
管理 npm 包package.json
里.eslintrc.js
babel.config.js
上面只是列出了几个主流配置,不出意外的话,如今你的项目里已经有 5 个配置文件了,在 JavaScript 这个前端万能脚本语言的粘合下,这些配置文件还能够互相引用互相耦合,复杂度搞成这样,开发体验尚未 iOS Android 的一半好。git
若是你认为我只是单纯的批评前端那你就理解错了,我想表达的是,这么复杂的配置都能搞定,iOS Android 的项目配置还不是手到擒来?
iOS 项目主要有两个点:project.pbxproj 和 CocoaPods。这两块儿的知识了解后,升级 RN 就彻底不虚了。
project.pbxproj
就是一个 iOS 项目的配置文件,从数据结构特色上有些像 JSON,年龄能够追溯到 NeXT,可读性基本为 0,每次 git 合并都是纯黑的噩梦。不信你瞅瞅下图,这是给人看的吗。
可读性这么差的东西能传下来,其实全靠 XCode 这个 IDE 给它续命。咱们每次在 XCode 里修改的配置,例如 Build Settings
等选项,最后都会反映到 project.pbxproj
这个配置文件上,也算是一种另类 DSL 了。
project.pbxproj
相关的知识我推荐下面几篇文章,阅读后会让你对 iOS 编译打包流程有个更深的了解:
project.pbxproj
文件的一些特色project.pbxproj
文件的对应关系有着更深入的了解CocoaPods 是一个负责管理 iOS 项目中第三方开源库的工具,目前主流 iOS 工程都是用 CocoaPods 管理第三方库的。
React Native 在 0.60 里终于用上了 CocoaPods,和 iOS 社区步调一致了起来。这样作的好处就是后续维护和迭代的压力会小不少,鬼知道我之前升级各类 iOS SDK 的日子是怎么熬过来的。
相对 project.pbxproj
,CocoaPods 无疑简单了很多,写配置脚本的 Ruby 语言也比较清爽,Podfile 的可读性要高不少。
platform :ios, '9.0' require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' target '项目名称' do pod 'React', :path => '../node_modules/react-native/' pod 'React-Core', :path => '../node_modules/react-native/React' use_native_modules! end
CocoaPods 的学习资料能够参考下文,不够的话自行搜索便可:
Android 的项目配置主要是由 gradle 文件控制的,gradle 文件又由 Groovy 这门 JVM 系的脚本语言书写。到这里思路就很明显了,咱们只要了解一些 Groovy 的语法和 gradle 的写法,就能读懂和修改 Android 的配置文件了。在这里我推荐一些相关教程,读完后就会有个大体的了解:
学习了基础的语法后,再回到 Android 工程上来。Android 的项目配置主要由 3 个文件控制,升级时冲突较多的也是这 3 个文件:
settings.gradle
:用来指示 Gradle 在构建应用时应将哪些模块包含在内build.gradle
:定义适用于项目中全部模块的构建配置app/build.gradle
:定义 App 的构建配置我的认为 Android 的 Gradle 配置仍是比较容易入门的,由于 gradle 文件有个好处,能够随意的添加注释。你们能够花点儿时间把每一个配置项都加上注释,这样在升级改动过程当中就不容易发怵。
React Native 官方在 2019 年 7 月 0.60 大版本更新时,推出了 Upgrade Helper 这个 Diff 小工具。经过这个工具咱们能够方便的看出版本更新时各个配置脚本的改动,很是的方便。
RN 版本升级时,个人升级流程通常是这样的:
README.md
文件,是否须要同步升级在更新过程当中,我的建议 git commit 操做要尽可能原子化,方便后续复盘和回滚,当心驶得万年船。
在我实际升级中,由于 React Native 0.59 到 0.60 有很是大的变更,而且业务较为复杂,升级 0.60 花了两个星期的时间:iOS 一周,Android 一周;0.61 和 0.62 的升级就比较简单了,大概一两个小时就能够升级好。
2019 年 7 月 3 日 Facebook 官方发布了 React Native 0.60,这是一次很是大的版本更新,虽然没有添加新的功能,可是在底层上作了不少优化,向主流配置靠齐:
react-native link *
了0.60 升级时必定要有耐心,不可能一次性成功的,建议参考 Upgrade Helper 和 Upgrade to React Native 0.60 这篇博文,我会对文中没有说明的地方进行补充。
升级前先确保相关第三方包已是最新版本。
JavaScript 这里相对来讲好升级一些,毕竟是前端程序员的主场。根据 Diff 差别升级版本号后,还须要注意如下几点:
NetInfo、WebView 和 Geolocation 从 React Native 中移除,交给 react-native-community 社区维护。因此咱们须要修改 import
时的路径。
Slider、AsyncStorage、CameraRoll、Clipboard 等组件也有移除计划,此次升级也能够顺便迁移一下。
值得注意的是,react-native-webview 在一次更新中为了响应 App Store 政策,已经移除了 UIWebView,只支持 WKWebView。若是你作过移动端的适配,你确定明白 WKWebview 对 cookie 支持不太友好,这里须要重点回归测试一下;另一点是若是 RN 和 H5 网页是经过 postMessage
的方式交互,相关 API 也有一些不兼容更新,这里须要重点适配一下,具体细节能够看文档。
SwipeableFlatList 是 React Native 在 0.5X 某个版本提供的侧滑删除列表组件,虽然一直没有官方文档中放出来,可是社区上已经有不少人在使用了。可能对这个组件的实现不太满意,官方在 0.60 里删除了这个组件。为了避免让项目报错,咱们可能须要把 SwipeableFlatList 相关的源码拿出来本身手动维护一下,有人把相关代码提出来维护了一个 npm 包——react-native-swipeable-lists,你们能够引入暂时过分一下。
0.60 版本的 React Native 支持 CocoaPods,2020 年了,RN 终于支持 CocoaPods 了,没有 CocoaPods 的时代,为了使用一些 iOS 第三方库,咱们必须手动把库文件拖到主工程里,升级和维护很是不方便。由于 0.61 版本 CocoaPods 是惟一可选包管理方案,因此强烈建议直接升级使用。
迁移 CocoaPods 前,先在 CLI 里输入一下命令 unlink Native Modules:
react-native unlink
unlink 后就要迁移到 CocoaPods 了。迁移前确保 Ruby 和 CocoaPods 已经安装成功,具体的安装过程不是本文重点就不展开了,没有安装的同窗自行 Google 搜索。
咱们在 ios 目录里新建一个文件 Podfile
,在里面输入如下代码:
platform :ios, '9.0' require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' target '项目名称' do pod 'React', :path => '../node_modules/react-native/' pod 'React-Core', :path => '../node_modules/react-native/React' pod 'React-DevSupport', :path => '../node_modules/react-native/React' pod 'React-RCTActionSheet', :path => '../node_modules/react-native/Libraries/ActionSheetIOS' pod 'React-RCTAnimation', :path => '../node_modules/react-native/Libraries/NativeAnimation' pod 'React-RCTBlob', :path => '../node_modules/react-native/Libraries/Blob' pod 'React-RCTImage', :path => '../node_modules/react-native/Libraries/Image' pod 'React-RCTLinking', :path => '../node_modules/react-native/Libraries/LinkingIOS' pod 'React-RCTNetwork', :path => '../node_modules/react-native/Libraries/Network' pod 'React-RCTSettings', :path => '../node_modules/react-native/Libraries/Settings' pod 'React-RCTText', :path => '../node_modules/react-native/Libraries/Text' pod 'React-RCTVibration', :path => '../node_modules/react-native/Libraries/Vibration' pod 'React-RCTWebSocket', :path => '../node_modules/react-native/Libraries/WebSocket' pod 'React-cxxreact', :path => '../node_modules/react-native/ReactCommon/cxxreact' pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi' pod 'React-jsiexecutor', :path => '../node_modules/react-native/ReactCommon/jsiexecutor' pod 'React-jsinspector', :path => '../node_modules/react-native/ReactCommon/jsinspector' pod 'yoga', :path => '../node_modules/react-native/ReactCommon/yoga' pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec' pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec' pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec' target '项目名称Tests' do inherit! :search_paths # Pods for testing end use_native_modules! end
上面这段代码,pod
开头的都是从 node_modules
目录导入 react-native
相关的官方代码。下面两行代码是实现 autolink 的功能:
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' target '项目名称' do ... use_native_modules! end
Podfile
配置好后,就在 ios 文件夹下运行 pod install
,安装相关依赖。
安装成功后会生成一个 xcworkspace 空间,这时候你须要退出当前的 xcodeproj 项目,打开 xcworkspace。
在 xcworkspace 里,首先有两个顶层文件夹,一个是你的 xcodeproj 项目,一个是 Pods 文件夹(左图):前者包含着你的业务代码,后者管理者安装的第三方库文件。这时候须要手动把 你的项目/Libraries
目录下的 *.xcodeproj
文件手动删除(右图红框 ➊),由于他们已经存在于 Pods 文件夹里了(右图红框 ➋)。
上一步修改了 React Native 项目的引用方式,但还有一个问题,那就是寻址的头文件路径并无修改过来,咱们能够观察下面两张图:
$(SRCROOT)/../node_modules/*
$(PODS_CONFIGURATION_BUILD_DIR)/*
当时这个变化卡了我一天,并且这个变化是在 project.pbxproj
中的,很是难以阅读就忽略掉了。后来经过新建一个 RN 新项目发现了问题。解决方法是删除原来的 Header Search Path 内容,手动把新的路径添加进去。
上面两步作完后能够尝试 build 一下项目,大几率你会发现仍是 build 不起来。由于错误缘由千奇百怪我也没法一一覆盖,这里仍是问 Google 比较方便。
到这一步假设你已经 Build 起来 iOS 项目了,这时候你会发现一个问题,以前 iOS build 成功后,会自动启动一个 node 服务器编译 javascript 文件,更新后并无自动启动 node 服务器,须要咱们手动 npm run start
启动 node 服务器,很是的不方便。
问题出在哪里呢?缘由是在原来的构建方式里,Libraries 下的 React.xcodeproj
有个 Start Packager 脚本,这个脚本会在项目 build 成功后自动启动一个 node 服务器:
迁移到 Pods 后,这个脚本就没有了,须要咱们在主工程里手动添加一下。添加方式也很简单,我在下图也标注好了,点击项目文件夹,在 TARGETS
的 Build Phases
里点击 ➕,再点击 New Run Script Phase
新增一个脚本区域,而后把下面的代码填写进去:
export RCT_METRO_PORT="${RCT_METRO_PORT:=8081}" echo "export RCT_METRO_PORT=${RCT_METRO_PORT}" > "${SRCROOT}/../node_modules/react-native/scripts/.packager.env" if [ -z "${RCT_NO_LAUNCH_PACKAGER+xxx}" ] ; then if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then if ! curl -s "http://localhost:${RCT_METRO_PORT}/status" | grep -q "packager-status:running" ; then echo "Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly" exit 2 fi else open "$SRCROOT/../node_modules/react-native/scripts/launchPackager.command" || echo "Can't start packager automatically" fi fi
这个 Start Packager 脚本的位置也有些讲究,最好放在 Check Pods Manifest.lock
和 Compile Sources
之间,要否则启动 node 服务器时会致使报错。
随着 iPhone 产品线的增多,iPhone手机的尺寸也多了起来,原来一个尺寸配一个 LaunchImage
的方式逐渐变的再也不适用,这时候 官方建议用 LaunchScreen.storyboard
来制做启动屏,而且要求 2021 年全部 APP 都得改成此方案。
具体的配置网上有不少教程了,你们搜索参考配置就好。我我的参考了如下教程:
若是项目以前有配置过自动打包脚本,由于此次升级迁移到 workspace,因此也得对原来的打包脚本作一些修改:
xcodebuild archive -project 项目名称.xcodeproj
⬇️
xcodebuild archive -workspace 项目名称.xcworkspace
关于 xcodebuild 能够参考这两篇文章:
0.60 的 Android 更新主要是 3 点:
升级前先须要升级 Gradle 和 Groovy 的版本。具体细节参考 Upgrade Helper。
AndroidX 的推动主要是 Google 官方受够了 Android 目前混乱不堪的 android.support
,用一个统一的 androidx
来代替。升级跟着 Android 官方文档走就行,我主要参考了如下文档:
迁移工做主要是修改 import
路径,工做量可能有些大,但心理负担较小,本质上就是改了个名字,问题不大。
Autolinking 功能集成前先试试运行 react-native unlink
,看看能不能自动取消连接。若是取消失败,就要本身手动删除旧的 link 代码,加入新的 Autolinking 代码。下面我以 react-native-svg 这个第三方库为例进行说明:
1.检查 android/settings.gradle
,删除旧的 include
配置,加入下面新的代码:
rootProject.name = '你的项目' - include ':react-native-svg' - project(':react-native-svg').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-svg/android') + apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) include ':app'
2.检查 android/app/build.gradle
,删除旧的配置,文件的最后一行加入一行配置:
dependencies { - implementation project(':react-native-svg') } + apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
3.检查 MainApplication.java
,删除旧的引用:
- @Override - protected List<ReactPackage> getPackages() { - return Arrays.<ReactPackage>asList( - new MainReactPackage(), - new SvgPackage() - ); + @SuppressWarnings("UnnecessaryLocalVariable") + List<ReactPackage> packages = new PackageList(this).getPackages(); + return packages; - }
值得注意的是,咱们业务中颇有可能会本身封装一些 Native Module,通过上面的修改后,导入 Native Module 的方式也要作相应的修改,这里能够参考官方文档 Android Register the Module:
+ import com.your-app-name.CustomToastPackage; // <-- Add this line with your package name. protected List<ReactPackage> getPackages() { @SuppressWarnings("UnnecessaryLocalVariable") List<ReactPackage> packages = new PackageList(this).getPackages(); + packages.add(new CustomToastPackage()); // <-- Add this line with your package name. return packages; }
Hermes 是一个 Facebook 开源的 Javascript 引擎,和如今的 JSC 相比,在包体积和启动速度上有所优化。社区上已经有不少介绍 Hermes 的文章了,我找了几篇比较好的,若是对 Hermes 感兴趣能够移步查看。
Hermes 的相关特性不是本文重点,因此就很少介绍了。
Android 想要使用 Hermes 的话,必须得使用版本号大于 0.60.4 的 React Native,而且要对 android/app/build.gradle
作一些修改:
project.ext.react = [ - entryFile: "index.js" + entryFile: "index.js", + enableHermes: false, // clean and rebuild if changing ] - def useIntlJsc = false + def jscFlavor = 'org.webkit:android-jsc:+' dependencies { - if (useIntlJsc) { - implementation 'org.webkit:android-jsc-intl:+' - } else { - implementation 'org.webkit:android-jsc:+' - } + if (enableHermes) { + def hermesPath = "../../node_modules/hermesvm/android/"; + debugImplementation files(hermesPath + "hermes-debug.aar") + releaseImplementation files(hermesPath + "hermes-release.aar") + } else { + implementation jscFlavor + } }
上面只列出了主要变动,若是不想用 Hermes,能够彻底不作更改;若是想要尝试一下,最好仍是根据 Upgrade Helper 列出的详细变动进行修改,而后阅读 React Native 官网的 Using Hermes 进行配置与调试。
React Native 0.61 最主要的更新就是 Fast Refresh 的引入了,这个功能大大提高了开发体验。
Fast Refresh 的加入有两个好处,第一个是把 live reloading 和 hot reloading 两个功能合二为一并作了功能增强;第二个终于支持 Hooks 热更新了。虽然 0.59.10 已经支持 hooks,可是当时的函数式组件不支持热更新,开发体验过于差劲。升级到 React Native 0.61 后就可使用了。
总体来讲 0.61 的更新很小,一两个小时就能够完成升级。升级前建议参考 Upgrade Helper 和 Upgrade to React Native 0.61 这篇博文,我会对文中没有说明的地方进行补充。
JavaScript 这里主要是一些 API 的变更和升级,跟着报错信息修改就好,难度并不大。
React 升级到 16.9 后,componentWillMount
等 API 废弃,必须迁移到 UNSAFE_componentWillMount
等带有 UNSAFE_
前缀的 API。
主工程里这些 API 比较容易重构和替换,麻烦的是一些好久没有维护的第三方 JS 包,这时候须要本身手动 Fork 一份代码维护,或者替换同功能的正在维护的第三方包,这个属于技术债,只能一点一点克服。
更新后有些方法和组件的引用路径发生了变动,须要咱们适配一下:
1.ErrorUtils
默认绑定到 global 上,不须要 import ErrorUtils from ErrorUtils
导入了
2.RCTNetworking
引用路径发生改变,须要修改成:
const RCTNetworking = require('react-native/Libraries/Network/RCTNetworking');
3.Dimensions
导入方式也发生了改变,须要修改:
import Dimensions from 'Dimensions';
⬇️
import { Dimensions } from 'react-native';
0.61 以后,React Native iOS 端只支持经过 Cocoapods Link 了,若是 0.60 已经升级到 Cocoapods 了,那么此次的 iOS 升级将会很是快,只须要改动 Podfile 中一些库的导入路径就能够了。
具体的差别可见 Upgrade Helper,很是简单,比对修改后从新 pod install
就能够了。
0.61 的 Android 升级也比较简单,升级了 Gradle 版本,修改了 Hermes 的引用路径,跟着 Upgrade Helper 的 Diff 依次修改就可。
React Native 0.62 也是增强了开发者体验,RN 项目默认引入了 Flipper 这个 Facebook 制做的移动端调试工具,支持了 React DevTools v4,错误提示能够选择新的 LogBox,比原来的错误提示更加友好从而更容易定位问题。
除了开发体验的增强,此次更新还支持了 Dark Mode 模式,RN 以后就能够作暗黑模式的适配了。
总体来讲 0.62 的更新也很小,一两个小时就能够完成升级。升级前建议参考 Upgrade Helper 和 Upgrade to React Native 0.62 这篇博文,我会对文中没有说明的地方进行补充。
React Native 以前使用 Animated
API 时,useNativeDriver
默认值为 false,也就是说默认都是 JS 线程绘制动画。版本升级后须要显式指定 useNativeDriver
的值。我认为这个更新的意义在于每次使用 Animated
时,强迫开发者思考能不能让动画在 Native 线程运行,优化动画体验。
LogBox 这个功能在 0.62 里是默认关闭的,0.63 版本默认开启。0.62 里开启方式比较 Hack,须要按如下步骤操做:
1.项目根目录新建一个 before.js
,而后里面只写一行代码:
require('react-native').unstable_enableLogBox();
2.在 JS 全部文件的入口文件 index.js
的第一行里导入这个文件:
import './before';
上面两步必须严格执行,否则的话会有红屏报错。
Cocoapods 在这个版本里也有些改动,除去 Flipper 相关的 pod,改动很是小,根据 Upgrade Helper 中的 Diff 差别修改就好。
0.62 升级须要修改一些 Swift 相关的配置,具体升级流程可见 React Native 0.62 upgrade (Xcode)
0.61 的 Android 升级也比较简单,升级了 Gradle 版本,除去 Flipper 相关的更新,改动很是小,跟着 Upgrade Helper 的 Diff 依次修改就可。
0.62 以后,Flipper 在 RN 的项目里是默认添加的,能够方便的查看 Layout、network 和 log 等信息。
旧项目升级时,Flipper 实际上是可选的,安装有些波折,上手体验了一下感受以下(版本为 0.52.1):
console.log
信息和 Native 的 log 信息和在一个应用里,比较方便查看React DevTools v4
,二者比对能够方便查看布局上面都是优势,缺点仍是有很多的,下面我说说我用下来感受到的不足:
JSON.stringify
后的数据上面就是个人使用体验,要不要在项目中使用,我以为你们仍是亲自体验一下比较好。
若是要在项目中集成 Flipper,根据 Upgrade Helper 进行集成就好,难度不是很大。
上面就是 React Native 版本升级指南的内容了,本升级教程会持续更新,为了得到最佳体验能够查看阅读博客原文。
以为文章对你有用的话必定要记得点赞哦 🌟,谢谢你,这对我来讲真的很重要!
更多优秀文章推荐: