ReactNative升级至0.50.3
前言
本文主要记录升级项目的 ReactNative 框架版本(0.44 升到 0.50)过程中遇到的一些问题,主要包含三部分:
- iOS 编译
- 运行 JS
- android 编译
这次框架升级变动比较大,下面我们一步一步来解决。
以下是我使用的环境:
1 | 操作系统: OS X 10.13.1 |
下面的
VSCode 不能 Debug这一点真是费了好大功夫,最后看了一点源码才最终解决,一把心酸泪…详情下面会说到…
第三方编译库
由于 RN 0.45.0 后,需要依赖一些第三方库,这些库通过 npm 或 yarn 下载非常慢,所以可以先手动下载,放到此文件夹: ~/.rncache(如果路径不存在就手动创建一个)
以下是我用到的几个库(版本可能会有更新),如果手动下载有困难,可以找已经下载好的同学拿一下:
1 | boost_1_63_0.tar.gz |
这里面也有人分享了下载链接到百度网盘:
iOS RN 0.45以上版本所需的第三方编译库(boost等)
react-natvei-git-upgrade
RN 的版本升级,以前都要手动去改 pacakge.json 里的版本号,现在使用 react-native-git-upgrade 这个工具来进行,可以省掉很多工作。
接下来主要分为两部分来解决,一部分是编译报错,一部分是运行 JS 报错(红屏错误),以下是我的相关记录。
iOS 编译
首先执行一遍 yarn 命令,然后执行 react-native-git-upgrade
PS: 涉及到公司项目,下面关于目录的路径会以 xxx 等来代替
接下来会一个又一个的问题,下面会列出我遇到的问题,解决完一个后就用 Xcode 重新 run 一下
react-natvei-git-upgrade 报错
如果执行 react-native-git-upgrade 后报以下错误:

解决方法:
1 | # 先找到刚才执行 `react-native-git-upgrade` 命令后产生的一个 patch 文件 |
下面是我执行命令后截取产生的部分内容:
1 | Checking patch ios/xxx/Images.xcassets/Contents.json... |
之后会产生一些 .rej 后缀的文件,使用 vim(带颜色插件),可以看到有哪些改动,再手动去解决一下:
如我这个文件 project.pbxproj.rej,查看了下里面主要有两个变化:
- 添加一个 RCTBlob 库,手动将
node_modules/react-native/Libraries/Blob/RCTBlob.xcodeproj拖到 Xcode 工程的 Libraries 文件夹即可 - 修改打包脚本路径为:
shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh";,这个后面会说到
参考:
https://github.com/facebook/react-native/issues/12112#issuecomment-284491701
看了官方的
.gitignore文件,里面是没有忽略.flowconfig的,所以也建议不要忽略掉了
pod 错误
由于项目中 iOS 用了 CocoaPods,所以可能会报这个错(没用 CocoaPods 的可以忽略)
1 | {path_to_your_project}/ios/Pods/Pods.xcodeproj Couldn't load project |
只要重新安装一遍 pod 依赖就行:
1 | $ cd {path_to_your_project}/ios |
引用 RCTBridgeModule.h 错误 (Redefinition)
1 | /Users/xxx/projects/ReactNative/xxx/node_modules/react-native/React/Base/RCTBridgeModule.h:54:16: Redefinition of 'RCTMethodInfo' |
如果报以上 Redefinition 的错误,是因为以前使用了这样的方式来引进 RCTBridgeModule.h:
1 | #import "RCTBridgeModule.h" |
RN 0.48 后一定要使用以下方式引进了:
1 | #import <React/RCTBridgeModule.h> |
如果为了兼容旧版本,可以用宏来判断一下(注意一定要把 <React/RCTBridgeModule.h> 的判断放在前面):
1 | #if __has_include(<React/RCTBridgeModule.h>) |
如果是我们自己写的文件就直接改就行了,如果是第三方库的,就先去看下该库最新版有没适配了,有的话直接更新该库就行,没有的话就只能 fork 该项目后自己改了。
如我遇到的这个 RCTBEEPickerManager,去 github 看了下有适配了,所以直接升级就好了:

参考:
https://github.com/facebook/react-native/issues/15775
UMMobClick
1 | /Users/xxx/projects/ReactNative/xxx/node_modules/rn-umeng/ios/RCTUmeng/RCTUmeng/RCTUmeng.m:11:9: 'UMMobClick/MobClick.h' file not found |
因为友盟是通过软链把 framework 链接过去的,不知道为啥有时 yarn install 或 npm install 后,那个软链接不见了,所以只能手动重新做一下软链接:
1 | cd ./node_modules/rn-umeng/ios/RCTUmeng/RCTUmeng/UMAnalytics_Sdk/UMMobClick.framework/Versions/ && ln -s A Current && cd .. && ln -s Versions/Current/Headers/ Headers && ln -s Versions/Current/UMMobClick UMMobClick && cd ../../../../../../../ |
env.json
1 | (null): error: /Users/xxx/projects/ReactNative/xxx/ios/xxx/env.json: No such file or directory |
env.json 这是我用来做一些环境配置的东西,如果没用到的话可以忽略这条。
由于各人的环境(如 ip)是不一样的,为了避免冲突,所以将此文件放进了 .gitignore 里,这里就手动复制一下 .env.json.example 稍微改下后缀和里面内容就行了
react-native-xcode.sh
1 | /Users/xxx/Library/Developer/Xcode/DerivedData/xxx-bghjpnetkufdnqgonitwrdmmbxdw/Build/Intermediates.noindex/xxx.build/Debug-iphonesimulator/xxx.build/Script-00DD1BFF1BD5951E006B06BC.sh: line 3: ../node_modules/react-native/packager/react-native-xcode.sh: No such file or directory |
看了下源码,现在用来打包 js 代码和图片的脚本的路径已经变了,以前是在这里:
1 | export NODE_BINARY=node |
现在要换为以下路径(在 Xcode 的 Build Phases 里的 Bundle React Native code and images 里改):
1 | export NODE_BINARY=node |
小结
经过以上修改,我的项目就能编译成功了,你的项目可能还会遇到其它坑,这个就要自行挖掘善用 google 了。
接下来就看下跑起来后 js 报的一些错误
iOS 运行
编译成功后,会遇到 js 报的错误,正常是会报红屏出来。不过发现会因为有些错误,红屏不能在启动后自动出现,需要按 Home 键回到桌面再点击图标进入,才会显示红屏错误。
后来发现是因为用了 react-native-splash-screen 这个库,这个库是用来解决 RN 启动时多次闪屏的问题,原理是让 mainRunloop 一直循环等待:
1 | + (void)show { |
在 js 加载到自己的入口页面后,手动调用 hide 方法隐藏掉:
1 | + (void)hide { |
如果 JS Bundle 没能正常加载,会导致我们自己设置的 hide 入口一直调用不到,所以闪屏页会一直卡在那里,看不到红屏错误。
其实上面的 show 方法里,有监听 js 加载错误的通知,在加载失败时会自动调用 hide 方法,以前版本是会 post 一个 RCTJavaScriptDidFailToLoadNotification 通知。
不过查看 RN 0.50 的源码后发现,在 JS 加载失败时(比如说编译到真机,设置的地址是 http://127.0.0.1:8081/index.ios.bundle?platform=ios&dev=true,但是真机又没有设置代理,所以真机是访问不到 127.0.0.1 上的 JS Bundle),不会 post 一个 RCTJavaScriptDidFailToLoadNotification 的通知,跟踪代码到 RCTCxxBridge.m 里的这个方法:
1 | - (void)loadSource:(RCTSourceLoadBlock)_onSourceLoad onProgress:(RCTSourceLoadProgressBlock)onProgress { |
上面 onComplete 的 block 里,跑进 error 里就直接 return 了,正常来说应该要调用 onSourceLoad(error, source),里面会判断 error 不为空,就调用 handleError 方法,发送 RCTJavaScriptDidFailToLoadNotification 的通知,不知道为什么在这里不调用了。
目前只能回到桌面再进来才能看到红屏页面了,不过下面的 Reload JS 按钮是点击不了的,或者是在 AppDelegate.m 里调用 show 方法后,定时一些时间后调用 hide 方法:
1 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { |
PS: 如果在升级前开了
Debug JS Remotely,可能会看不到具体在哪个文件报错,这时候只能先卸载掉桌面的 app 重新安装一次了
index.js
以前入口文件是使用两个文件来区分 ios 跟 android:
1 | index.ios.js / index.android.js |
现在统一使用一个 index.js 文件了,如果项目根目录没有这个,需要手动创建一下,再整合一下以前两个文件的内容。
使用 index.js 后,AppDelegate.m 里如果有用了 index.ios.bundle,也改为 index.bundle。
图片
由于历史原因,少部分图片引用时将 @2x 或是 @3x 或是 .ios 这个后缀也写进去了,现在这样会报错了:

1 | 如: |
全局搜索 @2x.png 及 @3x.png 将 js 文件里 用到的去掉就行了(注意非 js 文件就不要改了)
EventEmitter 引用错误

根据上面报错路径: ./node_modules/react-native-root-siblings/lib/AppRegistryInjection.js
查看源码发现是因为新版 RN 的 EventEmitter 的路径已经变了,看了下这个 react-native-root-siblings 是 react-native-root-toast 所依赖的一个库:
1 | import EventEmitter from 'react-native/Libraries/EventEmitter/EventEmitter'; |
去 github 看了下这个库已经适配了,所以直接升级该库就行了
PropTypes

以前引用 PropTypes 是从 React 里引:
1 | import React, { PropTypes } from 'react'; |
现在已经完全废弃了,需要另外安装这个库: prop-types
1 | $ npm install --save prop-types |
然后单独引进:
1 | import PropTypes from 'prop-types'; // ES6 |
如果是我们自己写的文件就直接改就行了,如果是第三方库的,就先去看下该库最新版有没适配了,有的话直接更新该库就行,没有的话就只能 fork 该项目后自己改了。
PS: 这里相当多地方要改,花了老多时间一个一个改…
另外,以前使用 View.proptypes 的,要改用 ViewProptypes,如:
1 | import { ViewProptypes } from 'react-native' |
React.createClass

ES5 可以使用以下来创建一个类:
1 | var xxx = React.createClass({}) |
现在新版 RN 完全废弃这种写法了,要么单独引进 create-react-class ,要么使用 ES6 的写法:
1 | export default class xxx extends React.xxxyyy {} |
其中还需要一起修改的写法包括属性、state、方法声明,去掉方法间逗号等,以下是 ES5 的写法:
1 | var xxx = React.createClass({ |
要改为 ES6 的写法:
1 | export default class xxx extends React.xxxyyy { |
需要注意的是,使用 create-react-class 时,会自动绑定 this(https://reactjs.org/docs/react-without-es6.html),所以修改为 ES6 写法,要注意 this 的绑定,像这次就遇到一个地方需要手动绑定一下:
1 | export default class xxx extends React.Component { |
Image 作为背景

以前如果要用一张图片做背景,会在 Image 里包含内容:
1 | <Image> |
现在已经废弃了,要么给 Image 使用绝对定位来布局,要么使用 ImageBackground:
1 | import { ImageBackground } from 'react-native' |
查看 <ImageBackground> 的源码(此时查看的 RN 版本是 0.50.3),发现内部是用一个 View 包住一个 Image 及其 children。看注释说里面的 Image 的宽高跟外面 ImageBackground 设置的宽高有冲突,所以目前只能在内部的 Image 里再重新设置了一下宽高,后面等有完美的方案后会移除掉这个。
值得一提的是,这次适配中,以前用 Image 时是直接写了 style,如果 style 里有 resizeMode,就会报警告了,因为 View 是没有 resizeMode 这个样式的,所以要把样式通过 imageStyle 属性传进去。
ImageBackground 源码:
1 | render() { |
所以这次升级中,如果有用到 <Image> 包裹内容,需要改为 <ImageBackground>,并且如果原本的 style 里有用到 resizeMode,如:
1 | <Image style={resizeMode:'contain'}> |
要改为 imageStyle:
1 | <ImageBackground imageStyle={ resizeMode: 'contain' }> |
或是干脆将 resizeMode 作为一个属性传过去(个人比较喜欢这种),当然如果是其它 Image 独有的 style,就只能通过 imageStyle 传过去了:
1 | <ImageBackground resizeMode={'contain'}> |
换为 ImageBackground 后,布局可能会有点不一样,建议改完后实际看下效果再调整一下
还需要特别注意的是,因为我最开始全局搜 </Image> 来查找内部包含子控件的 Image,但是用到动画的就搜不出来了:</Animatable.Image>,所以还需要搜索一下这个改改。
这个控件是在进入该页面时才会报错的,所以改好后最好都看下,全部测试一遍
VSCode 不能 Debug
这是当时用的最新版本:
VSCode 版本: 1.18.0
react-native-tools 插件版本: 0.5.2
点击 VSCode 的 Debug 按钮时,报了以下错误:
1 | [Error] Error: Error while executing command 'react-native run-ios --simulator --no-packager': Error while executing command 'react-native run-ios --simulator --no-packager' |
试了下新建一个 RN 0.50.3 的工程也是不能 Debug,估计是 VSCode 或 react-native-tool 本身的问题,只能等其更新了,暂时使用 react-native-debugger 来 Debug 了。
以前也遇过升级版本后,VSCode 的调试用不了,真是心酸
更新:
看了下有人提了 issue 了:https://github.com/Microsoft/vscode-react-native/issues/586#issuecomment-343918763,只要更新插件版本为 0.5.3 就行了。不过发现新建的工程可以了,自己的项目还是不行,还得继续探索。
在 VSCode 里点击菜单栏的 “查看-输出”,打开一个窗口后,在该窗口右上角,选择 React Native: Run ios(注意这里默认是 React Native,要手动选择一下),这里会列出一些详细信息,在这里看到了具体的错误信息:
1 | > Linking xxx |
查了下最上面一句的错误 invalid byte sequence in US-ASCII,网上说是编码问题,要加上 utf8,但是这里不是自己的代码,有点不明所以,继续看下面的 error,报错在这一行:
1 | /Users/aevit/projects/ReactNative/xxx/node_modules/react-native/local-cli/runIOS/runIOS.js:182:24 |
这一句报错了 xcpretty.stdin.write(data);,打印了一下 data:
1 | console.log(data.toString()); |
结果发现这里面的内容有中文,怀疑是中文导致的,试了下把中文换掉,成功了!感动。
至此,终于又可以使用 VSCode 调试了…
RCTTextField
iOS 里以前这个控件是继承自 UITextField,现在是继承自 RCTTextInput,里面 .m 文件里包含这一个输入控件:
1 | @property (nonatomic, readonly) UIView<RCTBackedTextInputViewProtocol> *backedTextInputView; |
这个控件没有暴露在 .h 文件里,所以我们项目中如果用了自定义键盘(赋值给 inputView),以前是这样直接取:
1 | UITextField *view = (UITextField*)[_bridge.uiManager viewForReactTag:reactTag]; |
现在这样会报错了,需要自己手动去查找一下,先这样简单粗暴地处理了:
1 | - (UITextView*)getRealTextView:(UITextView*)reactView { |
然后去调用一下方法:
1 | UITextField *view = (UITextField*)[_bridge.uiManager viewForReactTag:reactTag]; |
android 编译
createJSModules

1 | @Override |
从 RN 0.47 开始,以上写法会报错:
1 | 错误: 方法不会覆盖或实现超类型的方法 |
解决方法是将前面的 @Override 去掉
InnerClass
1 | Warning:Ignoring InnerClasses attribute for an anonymous inner class |
报错类似如上,解决方法是在 proguard-rules.pro 文件加上:
1 | -keepattributes InnerClasses |
react-native-splash-screen
使用这个库(3.0.6 版本),在启动时会报错 Can't convert to color: type=0x1:
1 | java.lang.UnsupportedOperationException: Can't convert to color: type=0x1 |
解决方法是在项目的 xxx/android/app/src/main/res/values/color.xml 里添加一个 primary_dark:
1 | <?xml version="1.0" encoding="utf-8"?> |
参考:
https://github.com/crazycodeboy/react-native-splash-screen/issues/123#issuecomment-342823345
Gif 播放报错
报错内容太多,这里截取部分:
1 | java.lang.NoClassDefFoundError: Failed resolution of: Lcom/facebook/imagepipeline/memory/PooledByteBuffer; |
从上面看是 gif 相关的错误,看到 build.gradle 里有引进 gif:
1 | dependencies { |
看了下是用到这个库:
https://github.com/facebook/fresco
添加多一个东西,并且更新版本就解决了:
1 | dependencies { |
总结
至此升级完毕,项目能跑起来了,不过由于这次好多东西都废弃了,一些第三方库的 api 也可能做了修改,所以可能还有一些隐藏的 bug 存在,最好重新完整测试一遍。
建议不要在 dev 阶段关闭 RN 的警告(console.disableYellowBox = false),这样能发现一些隐藏的 bug,或是一些以后将会被废弃的东西,及早修改。
2017-11-18 17:13
Aevit
深圳南山

摄影:Aevit 2015年8月 黄姚