QRN v1.4.0 base的官方RN版本从 0.20.0 同步到了 0.33.0
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 版本
findNodeHandle之前的写法:
require('React').findNodeHandle(this.refs.input);
现在的写法
var findNodeHandle = require('react/lib/findNodeHandle');
findNodeHandle(this.refs.input)
有一些其他的组件也和 findNodeHandle类似,不再挂在 React 中, 可能需要从 require('react/lib/*') 中引入
babel 插件修改为 babel-preset-qrnqunar-react-native-ext 所依赖的 babel 插件从babel-preset-qreact 修改为 babel-preset-qrn,在业务更新 node_Modules 的时候会自动替换。
css布局的改动如果出现图中送钱啦这种下面多出了大块空白的情况,只需要去掉组件布局中的 flex:1 即可解决:

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 中有相关的示例说明。
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>
)
}
}
NetInfov1.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);
});
Keyboard event监听键盘事件需要通过 Keyboard 注册
之前的写法:
const { DeviceEventEmitter } = require('react-native');
DeviceEventEmitter.addListener('keyboardWillShow', func);
现在的写法:
const { Keyboard } = require('react-native');
Keyboard.addListener('keyboardWillShow', func);
出现上面的问题把JSX里面的immutable List转成数组即可解决
官方0.32.0版本中,iOS 重构了CSS布局引擎,修改了一些组件的实现。
去掉了 RCTImageComponent.h
去掉了 RCTShadowVirtualImage.h
去掉了 RCTVirtualImageManager.h
RCTXCAssetImageLoader.h 修改为RCTLocalAssetImageLoader.h
Layout.h 修改为 CSSLayout.h 和 CSSNodeList.h
新增加了 JS 字体属性转换相关的类 RCTFont.h
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];
}
CSSNode 与 RCTShadowViewCSSNode 相关的头文件由 Layout.h 修改为 CSSLayout.h 和 CSSNodeList.h
CSSLayout.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;
}
在 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();
}
RCTScrollView 和 RCTScrollableProtocol去掉了RCTScrollableProtocol 中的 nativeScrollDelegate 属性,改用 addScrollListener 和 removeScrollListener 来添加删除 RCTScrollView 的 UIScrollViewDelegate 。点击查看官方说明
在 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; 不会导致插件在一开始就初始化,因此对插件初始化时机敏感的话注意检查。
ViewManager 的继承体系,与官方保持同步QRN 之前的 ViewManger 类继承了 ReactContextBaseJavaModule 类,现在已经去除和官方保持一致
** 需要注意的是这个改动会导致 在NativeModuls中也获取不到 这个继承自 ViewManager 的对象, ViewManager对象也不能使用 @ReactMethod 给 JS 暴露方法 **
如果需要调用和 某UI 组件相关的方法推荐是新建一个相关联的原生API模块,具体可以询问 hongbo.chen
QReactBaseActivity 类需要在 onDestroy() 生命周期中调用释放内存资源的方法。
调用释放方法示例如下:if (mReactRootView != null) {
mReactRootView.unmountReactApplication();
mReactRootView = null;
}
ReactRootView 在 Activity 被销毁的时候需要调用 QRNActivityHelper类中的 onDestroy() 方法释放内存资源。QReactNative 中的 createRootViewUseBaseActivityWithListener() 这种情况。
调用释放方法示例如下:qRnactivityHelper.onDestory();
TextInput 和 Toolbar 模块官方实现改动较大,有用到的业务线注意下这块ViewProps 的属性 Spacing.LEFT 和 Spacing.RIGHT 已经变成了 Spacing.START 和 Spacing.ENDdecomposedMatrix 方法官方最新 0.32.0 已经被移除, QRN 目前保留该方法,如果使用到的业务线请考虑升级成官方的最新方法 transformActivityEventListener接口已经更改,如果有继承这个接口的需要重新修改,否则会引发崩溃。在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'
}