QRN v1.4.0 base的官方RN版本从 0.20.0 同步到了 0.33.0

官方0.20.0 到 0.33.0 之间的改动

JS

1.1 React API 现在需要从 react 中引入

v1.4.0 QRNreact 版本为 15.3.0

0.32.0 开始, React Native 官方开始强制 React APIreact 中引入

之前的写法:

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-nativequnar-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布局的改动

官方0.28.0说明

如果出现图中送钱啦这种下面多出了大块空白的情况,只需要去掉组件布局中的 flex:1 即可解决:

1.5 Image组件

0.20.0 的官方 Image 支持 containcoverstretch 这3种 resizeMode

qunar-react-native 在官方 0.20.0 的基础上支持了更多的 resizeMode:

  • fitCenter (contain)
  • centerCrop (cover)
  • fitXy (stretch)
  • fitStart
  • fitEnd
  • focusCrop
  • center
  • centerInside

官方 0.32.00.20.0 基础上又添加了 centerrepeat(iOS only) 2种 resizeMode,而官方新增的 center 其实和 QRN 中添加的 centerInside 是一致的。因此 v1.4.0v1.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 手机上运行会出问题

listError 出现上面的问题把JSX里面的immutable List转成数组即可解决

iOS

官方0.32.0版本中,iOS 重构了CSS布局引擎,修改了一些组件的实现。

2.1 头文件变化

去掉了 RCTImageComponent.h
去掉了 RCTShadowVirtualImage.h
去掉了 RCTVirtualImageManager.h
RCTXCAssetImageLoader.h 修改为RCTLocalAssetImageLoader.h
Layout.h 修改为 CSSLayout.hCSSNodeList.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 CSSNodeRCTShadowView

  • CSSNode 相关的头文件由 Layout.h 修改为 CSSLayout.hCSSNodeList.h
    • CSSLayout.h 设置 node 中 css 属性的方法
    • CSSNodeList.hnodeList 相关的函数,包括新建、释放、获取长度、移除指定的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以及之前的版本中,因为RCTUIManagermethodQueue使用的是主线程,所有继承自 RCTViewManagerUI ComponentsRCT_EXPORT_METHOD 的方法默认是在主线程调用。

v1.4.0RCTUIManagermethodQueue 使用的是 QOS_CLASS_USER_INTERACTIVE 属性的的高优先级串行队列,所以需要检查继承自 RCTViewManager 组件的 RCT_EXPORT_METHOD的方式是否必须在主线程调用,如果需要则给组件添加下面的方法:

- (dispatch_queue_t)methodQueue
{
  return dispatch_get_main_queue();
}

2.5 RCTScrollViewRCTScrollableProtocol

去掉了RCTScrollableProtocol 中的 nativeScrollDelegate 属性,改用 addScrollListenerremoveScrollListener 来添加删除 RCTScrollViewUIScrollViewDelegate点击查看官方说明

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;
    }
  • 业务线自主使用 ReactRootViewActivity 被销毁的时候需要调用 QRNActivityHelper类中的 onDestroy() 方法释放内存资源。
    例如使用了 QReactNative 中的 createRootViewUseBaseActivityWithListener() 这种情况。 调用释放方法示例如下:
qRnactivityHelper.onDestory();

3.3 属性和接口变动

  1. TextInputToolbar 模块官方实现改动较大,有用到的业务线注意下这块
  2. ViewProps 的属性 Spacing.LEFTSpacing.RIGHT 已经变成了 Spacing.STARTSpacing.END
  3. decomposedMatrix 方法官方最新 0.32.0 已经被移除, QRN 目前保留该方法,如果使用到的业务线请考虑升级成官方的最新方法 transform
  4. 官方接口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'
    }