TL;DR
升级RN可以带来以下好处:
- Hot Reloading 支持函数组件(旧版只支持 extends Component 方式);
- React 版本升级到16.8.6,可以使用 hooks 等新 API;
- 自带支持 Typescript,只要用
.ts
或.tsx
后缀即可,无需改任何配置,与现有 js 文件可共存。
升级到 0.59 版本比 0.60 痛楚要小很多,只需要更改 App 构建配置以及部分原生组件的写法即可。
升级背景
事情的起因是谷歌宣布19年8月1号开始在Google Play上架的应用都必须支持64位体系,而RN版本对64位的支持从0.58才开始,而项目组使用的版本比这低,因此升级是在所难免的。考虑到升级后框架更稳定而且含有更多特性,事不宜迟马上开搞。
升级的第一个问题是该升到哪个版本。当前时间段最新的版本有0.59和0.60(吐槽下时隔四年RN还没1.0版本)。从0.60的 changelog 中我们发现有一个重大的 breaking change 就是 AndroidX support。这个改动不像支持64位体系那样改改编译参数就行,还必须改动原生代码,是个改动量非常大的升级(主要是针对第三方库以及项目中的原生代码改动较大),而特性并没有比 0.59 多太多,反而去掉了很多以前的内置组件,移到社区托管了。因此最终钦定 0.59.10 为升级目标版本。
对于用户以及开发人员而言,以下几点改动是比较明显的,即使不需要上架 Google Play 也可以参考一下以确定是否需要升级:
- Hot Reloading 支持函数组件(旧版只支持 extends Component 方式);
- React 版本升级到16.8.6,可以使用 hooks 等新 API;
- 自带支持 Typescript,只要用
.ts
或.tsx
后缀即可,无需改任何配置。
升级iOS
新版移除了 iOS 8.x 的支持,需要升级系统最低版本。另外,glog 依赖的名称变了。使用 Podfile的需要改动以下内容
-platform :ios, '8.0'
+platform :ios, '9.0'
- pod 'GLog', :podspec => '../node_modules/react-native/third-party-podspecs/GLog.podspec'
+ pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec'
新版的输入框组件(TextInput)类继承体系改了,若做了相关扩展需要同步修改
原生封装的组件改动可能较多,需要多进行测试(自定义以及第三方原生扩展)。
升级Android
升级RN依赖后 appcompat-v7
会使用 28.0.0 版本,和项目中已使用的版本冲突,可使用以下方式降级处理。
build.gradle
allprojects {
configurations.all {
resolutionStrategy {
force "com.android.support:appcompat-v7:26.0.1"
force "com.android.support:support-v4:26.0.1"
}
}
}
RN 原生扩展组件很多时候是依赖于具体 RN 版本的,需要到各自官网查看升级描述。
升级React
使用 babel 插件转换旧代码
由于 React 需要同步升级,而新版由于引入 fiber 渲染,原先的三个生命周期变得不再安全而计划废弃并且改了名字,需要进行替换。
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
由于尚有部分第三方库未升级,不方便在项目中修改,故引入 babel 插件精心统一替换。需添加react-rename-unsafe-lifecycle
插件;若需要使用装饰器语法(如搭配redux或mobx),需要添加@babel/plugin-proposal-decorators
插件。
babel.config.js
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
plugins: [
'react-rename-unsafe-lifecycle',
'@babel/plugin-proposal-decorators', { legacy: true }
]
};
package.json
{
"dependencies": {
"@babel/core": "^7.4.5",
"@babel/runtime": "^7.4.5",
"react": "16.8.6",
"react-native": "0.59.10",
"babel-plugin-react-rename-unsafe-lifecycle": "^1.0.4",
"metro-react-native-babel-preset": "^0.54.1"
}
}
虽然使用以上插件可以暂时运行,但在特殊场景下是不安全的,并且后续会废弃掉,因此后续应该持续跟进,把这些接口用新接口重写。
改写 componentWillMount()
- 用于发起网络请求的移动到
componentDidMount()
中 - 根据传入的 props 计算组件属性(this.xxx)的(同步),视情况移动到
constructor
中
改写 componentWillReceiveProps()
根据使用场景不同,按如下方式替换
- 需要根据传入的 props 执行副作用,改用
componentDidUpdate()
(注意state改变也会执行componentDidUpdate
,需多加判断) - 需要根据传入的 props 计算新state,参考 这里(比较少见)
改写 componentWillUpdate()
相关代码移动到 componentDidUpdate()
或 shouldComponentUpdate()
React Native (js)
官方移除组件
由于升级后 RN 官方仓库进行过瘦身,很多基本功能都移到社区维护了,用到了的需要手动添加回来。大部分组件规划在 0.60 正式移除,0.59 还能继续用,可考虑下次升级再处理。
准备废弃的组件列表
- ImageStore
- ListView
- MaskedViewIOS
- Slider
- SwipeableListView
- ViewPagerAndroid
- WebView
- AlertIOS
- AsyncStorage
- NetInfo
非公开接口
有部分接口旧版本是没有公开的,需要用的只能根据路径引用。新版对路径进行了调整,导致引用报错。以下接口在新版做了改动,但已通过公开接口暴露出来,可以直接 import { XXX } from "react-native"
使用。
- ColorPropType
- EdgeInsetsPropType
- PointPropType
- ViewPropTypes
行为变化的组件
这部分改动最麻烦,只能发现一个改一个。目前发现以下问题:
- FlatList 初始化时不会调用 onEndReached
- ScrollView 的 scrollTo 方法 y 坐标不支持负值,scrollTo({y: -100}) 和 scrollTo({y: -0}) 效果一样
- WebView 的 onMessage 接收到的数据会被 encodeURIComponent 两次,可以通过添加 useWebKit 属性解决,恢复旧版行为
- react-native-webview 是从 RN 核心中抽离的 webview 组件,webview 发送消息需要用 window.ReactNativeWebView.postMessage 方法,和旧版的 window.postMessage 方式差异太大,不兼容线上代码,暂不考虑
- NativeModules 改成了只读,需要修改只能复制一份修改然后替换整个 NativeModules
- 原生封装的UI组件导出的常量使用方式改变,例如原来是通过
UIManager.RCTWebView.Commands.goForward
访问,现在需要使用UIManager.getViewManagerConfig('RCTWebView').Commands.goForward
整个升级流程下来还是挺顺畅的,处理原生的构建花了一点时间,后续 js 部分的修改很多时候只要看控制台报错就可以,提示还是很清晰的。
App能跑起来后,基本功能没太大问题,但某些小角落可能会出现一些交互异常甚至奔溃,主要都是因为组件的 API 或者行为改变引起,需要做好充分的回归测试。