QRN v1.4.0 base的官方RN版本从 0.20.0 同步到了 0.33.0
JS
1.1 React API 现在需要从 react 中引入
v1.4.0 QRN 中 react 版本为 15.3.0
从 0.32.0 开始, React Native 官方开始强制 React API 从 react 中引入
之前的写法:
import React, { Component, View } from 'react-native';
现在的写法:
import React, { Component } from 'react';
import { View } from 'react-native';
React API 中包含了下面的组件:
import { Component,
PropTypes,
createClass,
PureComponent,
Children,
createElement,
cloneElement,
isValidElement,
createFactory
} from 'react';
为了兼容现在线上的业务,在 qunar-react-native 中,react 被同时注入到了 react-native 和 qunar-react-native 中。不过还是建议使用官方推荐的方式从 react 中引入 React API。
需要注意的是 v1.3.0 不会依赖 react, 但是 v1.4.0 中会依赖至少 15.3.0 版本的 react
那么业务RN工程升级 node_modules 时需要检查 package.json 中依赖的其他的模块是否依赖低版本的 react ,导致qnpm install 安装的是低版本的 react
可以通过查看 node_moduels/react/package.json 来确定安装的 react 版本
1.2 findNodeHandle
之前的写法:
require('React').findNodeHandle(this.refs.input);
现在的写法
var findNodeHandle = require('react/lib/findNodeHandle');
findNodeHandle(this.refs.input)
有一些其他的组件也和 findNodeHandle类似,不再挂在 React 中, 可能需要从 require('react/lib/*') 中引入
1.3 babel 插件修改为 babel-preset-qrn
qunar-react-native-ext 所依赖的 babel 插件从babel-preset-qreact 修改为 babel-preset-qrn,在业务更新 node_Modules 的时候会自动替换。
1.4 css布局的改动
如果出现图中送钱啦这种下面多出了大块空白的情况,只需要去掉组件布局中的 flex:1 即可解决:

1.5 Image组件
0.20.0 的官方 Image 支持 contain 、 cover 和 stretch 这3种 resizeMode。
qunar-react-native 在官方 0.20.0 的基础上支持了更多的 resizeMode:
fitCenter (contain)centerCrop (cover)fitXy (stretch)fitStartfitEndfocusCropcentercenterInside
官方 0.32.0 在 0.20.0 基础上又添加了 center 和 repeat(iOS only) 2种 resizeMode,而官方新增的 center 其实和 QRN 中添加的 centerInside 是一致的。因此 v1.4.0 在 v1.3.0的基础上只增加了 repeat(iOS only)。
业务使用中需要注意官方文档对 center 的说明其实对应 qunar-react-native 中的 centerInside。
关于 resizeMode 的具体说明可以查看 ImageResizeMode.js , 在QRN 的 demo 中有相关的示例说明。
1.6 react-redux
因为 v1.4.0 版本依赖的 react 版本为 15.3.0 ,所以如果使用旧的 react-redux 写法则会提示:
With React 0.14 and later versions, you no longer need to wrap <Provider> child into a function.
解决方法,修改:
class OrderListView extends Component {
render() {
return (
<Provider store={store}>
{() => <OrderCenter {...this.props}/>}
</Provider>
)
}
}
为
class OrderListView extends Component {
render() {
return (
<Provider store={store}>
<OrderCenter {...this.props}/>
</Provider>
)
}
}
1.7 NetInfo
v1.4.0 中因为官方使用消息的方式重新写了 NetInfo,导致调用 NetInfo.isConnected 返回的网络状态永远都是 false
NetInfo.isConnected.fetch().then(isConnected => {
alert(isConnected);// isConnected always be false
});
这个为官方的Bug,不好修复,可以使用下面的方法临时fix
var netChange = (isConnected)=>{alert(isConnected)};
NetInfo.isConnected.fetch().then().done(() => {
NetInfo.isConnected.addEventListener('change', netChange);
});
1.8 Keyboard event
监听键盘事件需要通过 Keyboard 注册
之前的写法:
const { DeviceEventEmitter } = require('react-native');
DeviceEventEmitter.addListener('keyboardWillShow', func);
现在的写法:
const { Keyboard } = require('react-native');
Keyboard.addListener('keyboardWillShow', func);
1.9 minify后 JSX里面 使用 immutable List 在安卓和个别 iPhone 手机上运行会出问题
出现上面的问题把JSX里面的immutable List转成数组即可解决
iOS
官方0.32.0版本中,iOS 重构了CSS布局引擎,修改了一些组件的实现。
2.1 头文件变化
去掉了 RCTImageComponent.h去掉了 RCTShadowVirtualImage.h去掉了 RCTVirtualImageManager.hRCTXCAssetImageLoader.h 修改为RCTLocalAssetImageLoader.hLayout.h 修改为 CSSLayout.h 和 CSSNodeList.h
新增加了 JS 字体属性转换相关的类 RCTFont.h
2.2 JS字体相关的属性转换从 RCTConvert 中单独剥离到 RCTFont 中
之前的写法:
RCT_CUSTOM_VIEW_PROPERTY(fontSize, CGFloat, RCTTextField)
{
view.font = [RCTConvert UIFont:view.font withSize:json ?: @(defaultView.font.pointSize)];
}
RCT_CUSTOM_VIEW_PROPERTY(fontWeight, NSString, __unused RCTTextField)
{
view.font = [RCTConvert UIFont:view.font withWeight:json]; // defaults to normal
}
RCT_CUSTOM_VIEW_PROPERTY(fontStyle, NSString, __unused RCTTextField)
{
view.font = [RCTConvert UIFont:view.font withStyle:json]; // defaults to normal
}
RCT_CUSTOM_VIEW_PROPERTY(fontFamily, NSString, RCTTextField)
{
view.font = [RCTConvert UIFont:view.font withFamily:json ?: defaultView.font.familyName];
}
现在的写法:
#import "RCTFont.h"
RCT_CUSTOM_VIEW_PROPERTY(fontSize, NSNumber, RCTTextField)
{
view.font = [RCTFont updateFont:view.font withSize:json ?: @(defaultView.font.pointSize)];
}
RCT_CUSTOM_VIEW_PROPERTY(fontWeight, NSString, __unused RCTTextField)
{
view.font = [RCTFont updateFont:view.font withWeight:json]; // defaults to normal
}
RCT_CUSTOM_VIEW_PROPERTY(fontStyle, NSString, __unused RCTTextField)
{
view.font = [RCTFont updateFont:view.font withStyle:json]; // defaults to normal
}
RCT_CUSTOM_VIEW_PROPERTY(fontFamily, NSString, RCTTextField)
{
view.font = [RCTFont updateFont:view.font withFamily:json ?: defaultView.font.familyName];
}
2.3 CSSNode 与 RCTShadowView
- 与
CSSNode相关的头文件由Layout.h修改为CSSLayout.h和CSSNodeList.hCSSLayout.h设置 node 中 css 属性的方法CSSNodeList.h与nodeList相关的函数,包括新建、释放、获取长度、移除指定的node等等
设置cssNode代码中之前的写法:
- (void)setFrame:(CGRect)frame
{
_cssNode->style.position[CSS_LEFT] = CGRectGetMinX(frame);
_cssNode->style.position[CSS_TOP] = CGRectGetMinY(frame);
_cssNode->style.dimensions[CSS_WIDTH] = CGRectGetWidth(frame);
_cssNode->style.dimensions[CSS_HEIGHT] = CGRectGetHeight(frame);
[self dirtyLayout];
}
现在的写法
- (void)setFrame:(CGRect)frame
{
CSSNodeStyleSetPositionLeft(_cssNode, CGRectGetMinX(frame));
CSSNodeStyleSetPositionTop(_cssNode, CGRectGetMinY(frame));
CSSNodeStyleSetWidth(_cssNode, CGRectGetWidth(frame));
CSSNodeStyleSetHeight(_cssNode, CGRectGetHeight(frame));
}
- 设置
ShadowView中 node 的measure函数修改为在init方法中使用CSSNodeSetMeasureFunc(self.cssNode, RCTMeasure);来实现,同时新增了RCTMeasure函数的参数。去掉了RCTShadowView中的fillCSSNode方法。点击查看官方说明
原来的写法
static css_dim_t RCTMeasure(void *context, float width, float height)
{
CGSize computedSize;
// cal computedSize
css_dim_t result;
result.dimensions[CSS_WIDTH] = RCTCeilPixelValue(computedSize.width);
result.dimensions[CSS_HEIGHT] = RCTCeilPixelValue(computedSize.height);
return result;
}
- (void)fillCSSNode:(css_node_t *)node
{
[super fillCSSNode:node];
node->measure = RCTMeasure;
}
现在的写法
- (instancetype)init{
self = [super init];
if (self) {
CSSNodeSetMeasureFunc(self.cssNode, RCTMeasure);
}
return self;
}
static CSSSize RCTMeasure(void *context, float width, CSSMeasureMode widthMode, float height, CSSMeasureMode heightMode)
{
CGSize computedSize;
// cal computedSize
CSSSize result;
result.width = RCTCeilPixelValue(computedSize.width);
result.height = RCTCeilPixelValue(computedSize.height);
}
- 去掉了
RCTShadowView中[RCTShadowView dirtyLayout]方法,使用CSSNodeMarkDirty(self.cssNode)。点击查看官方说明
RCTShadowView不再使用self.cssNode->children_count = 0而是通过[RCTShadowView isCSSLeaf]来跳过子元素的 css 设置。点击查看官方说明
原来的写法
- (void)insertReactSubview:(RCTShadowView *)subview atIndex:(NSInteger)atIndex
{
[super insertReactSubview:subview atIndex:atIndex];
self.cssNode->children_count = 0;
}
- (void)removeReactSubview:(RCTShadowView *)subview
{
[super removeReactSubview:subview];
self.cssNode->children_count = 0;
}
现在的写法
- (BOOL)isCSSLeaf{
return YES;
}
2.4 UI 组件方法的调用的线程改变
在 v1.3.0以及之前的版本中,因为RCTUIManager中methodQueue使用的是主线程,所有继承自 RCTViewManager 的 UI Components 中 RCT_EXPORT_METHOD 的方法默认是在主线程调用。
v1.4.0 中 RCTUIManager 的 methodQueue 使用的是 QOS_CLASS_USER_INTERACTIVE 属性的的高优先级串行队列,所以需要检查继承自 RCTViewManager 组件的 RCT_EXPORT_METHOD的方式是否必须在主线程调用,如果需要则给组件添加下面的方法:
- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();
}
2.5 RCTScrollView 和 RCTScrollableProtocol
去掉了RCTScrollableProtocol 中的 nativeScrollDelegate 属性,改用 addScrollListener 和 removeScrollListener 来添加删除 RCTScrollView 的 UIScrollViewDelegate 。点击查看官方说明
2.6 插件的初始化时机
在 v1.3.0 中,RCTBridgeModule的子类只要实现了 init 或者 constantsToExport 方法则会在bridge初始化时在主线程创建该对象,否则new对象的时机是在JS调用该对象的暴露的方法。
@synthesize bridge;
- (instancetype)init{
self = [super init];
if (self) {
}
return self;
}
在 v1.4.0 中,实现了 init 或者 constantsToExport 方法的RCTBridgeModule的子类会在bridge初始化时在主线程创建该对象。
- (instancetype)init{
self = [super init];
if (self) {
}
return self;
}
- (NSDictionary<NSString *, id> *)constantsToExport
{
return @{@"forceTouchAvailable": @(RCTForceTouchAvailable())};
}
@synthesize bridge; 不会导致插件在一开始就初始化,因此对插件初始化时机敏感的话注意检查。
Android
3.1 修改了 ViewManager 的继承体系,与官方保持同步
QRN 之前的 ViewManger 类继承了 ReactContextBaseJavaModule 类,现在已经去除和官方保持一致
需要注意的是这个改动会导致 在NativeModuls中也获取不到 这个继承自 ViewManager 的对象, ViewManager对象也不能使用 @ReactMethod 给 JS 暴露方法
如果需要调用和 某UI 组件相关的方法推荐是新建一个相关联的原生API模块,具体可以询问 hongbo.chen
3.2 需要手动代码中调用释放资源的代码,依据使用QRN方式不同,有以下两种情况。
- 继承于
QReactBaseActivity类需要在onDestroy()生命周期中调用释放内存资源的方法。 调用释放方法示例如下:
if (mReactRootView != null) {
mReactRootView.unmountReactApplication();
mReactRootView = null;
}
- 业务线自主使用
ReactRootView在Activity被销毁的时候需要调用QRNActivityHelper类中的onDestroy()方法释放内存资源。
例如使用了QReactNative中的createRootViewUseBaseActivityWithListener()这种情况。 调用释放方法示例如下:
qRnactivityHelper.onDestory();
3.3 属性和接口变动
TextInput和Toolbar模块官方实现改动较大,有用到的业务线注意下这块ViewProps的属性Spacing.LEFT和Spacing.RIGHT已经变成了Spacing.START和Spacing.ENDdecomposedMatrix方法官方最新0.32.0已经被移除,QRN目前保留该方法,如果使用到的业务线请考虑升级成官方的最新方法transform- 官方接口
ActivityEventListener接口已经更改,如果有继承这个接口的需要重新修改,否则会引发崩溃。3.4 本地依赖更新
在gradle的依赖配置文件(dependencies.gradle或者build.gradle)中,更新qrn的版本依赖。atomCompile 'com.qunar.spider:react:1.4.0@aar' compile ('com.qunar.react:qunar-react-native-dependence:1.4.0-SNAPSHOT@aar'){ exclude group: 'com.facebook.fresco', module: 'imagepipeline' exclude group: 'com.facebook.fresco', module: 'fbcore' }
