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

官方0.28.0说明

如果出现图中送钱啦这种下面多出了大块空白的情况,只需要去掉组件布局中的 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)
  • fitStart
  • fitEnd
  • focusCrop
  • center
  • centerInside

官方 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 手机上运行会出问题

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.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.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;
}

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 属性和接口变动

  1. TextInput 和 Toolbar 模块官方实现改动较大,有用到的业务线注意下这块
  2. ViewProps 的属性 Spacing.LEFT 和 Spacing.RIGHT 已经变成了 Spacing.START 和 Spacing.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'
    }