前言

React Native与传统的HybirdApp最大区别就是抛开WebView,使用JSC+原生组件的方式进行渲染,那么整个App启动/渲染流程又是怎样的呢?

React Native启动流程

首先从组件的角度来看下RN的启动流程:(Android为例)

  1. Native初始化,主要流程:ReactNativeHost -> Activity -> ReactRootView -> startReactApplication -> createReactContextInBackground(期间有模块/UI组件信息收集、JSC初始化等工作)
  2. 后台异步加载、执行JSBundle
  3. Native端执行setupReactContext初始化React上下文,调用JS端AppRegistry.runApplication(key,params),key为模块/组件名称,参数包含rootTag、initialProps
  4. JS端找到注册的对应启动组件,执行renderApplication渲染整个应用

renderApplication函数中会执行:

ReactNative.render(
<AppContainer>
<RootComponent
{...initialProps}
rootTag={rootTag}
/>
</AppContainer>,
rootTag
);

其中ReactNative是在React库中定义的,AppContainer是一个JS组件,使用View包裹了根组件,开发时工具InspectorYellowBox都是在这个组件中加载,RootComponent是传入的根组件。

JS端注册组件:(在第2步执行JSBundle时)

AppRegistry.registerComponent('TiebaNext', rootComponent);

*仅在JS端处理,记录在一个Map中。

Android端定义启动组件,Activity中,继承ReactActivity:(在第1步时调用)

@Override
protected String getMainComponentName() {
return "TiebaNext";
}

iOS端定义启动组件:

self.rctRootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"TiebaNext"
initialProperties:nil
launchOptions:nil];

简单说就是Native初始化 -> 加载JS,JS端注册组件 -> 端上调用JS端run方法,传入入口组件名称 -> JS端启动渲染流程。

React Native渲染流程

React的渲染都是以组件为单位,上面已经分析了,启动的最后阶段就是JS端开始渲染根组件。首先我们先看下React的组件是怎么编写的,以及他的生命周期:(熟悉React可略过)

一个例子,无网络提示组件:

(例子语言Typescript)

// 组件的属性定义
interface PropsDefine {
// 组件宽度
width: number
// 组件高度
height: number
// 点击刷新按钮回调,可选
onClickRefresh?: () => void
}
export class NoNetwork extends React.Component<PropsDefine, {}> { // 组件无状态,定义为空:{}
// 组件的默认属性定义,单例,实例间共享
static defaultProps = {
onClickRefresh: () => { }
} render() {
let {width, height} = this.props return (
<View style={[Styles.panel, {
width: width,
height: height,
}]}>
<View style={Styles.picBlock}>
<Image source={Styles.picUrl}/>
</View>
<View style={Styles.textBlock}>
<Text style={Styles.text}>你的网络好像不给力</Text>
<Text style={Styles.text}>点击按钮刷新</Text>
</View>
<TouchableOpacity style={Styles.button} onPress={this.props.onClickRefresh}>
<Text style={Styles.buttonText}>刷新</Text>
</TouchableOpacity>
</View>
)
}
}

跟端上组件开发一样,React组件也定义了组件的生命周期:

实例化

  • getDefaultProps

    组件类型首次实例化时初始化默认props属性,多实例共享
  • getInitialState

    实例化时初始化默认state属性
  • componentWillMount

    在渲染之前触发一次
  • render

    渲染函数,返回DOM结构
  • componentDidMount

    在渲染之后触发一次

有需要重新渲染(props变更或者setState改变state时)

  • componentWillReceiveProps

    组件接收到新的props时调用,并将其作为参数nextProps使用,可在此更改组件state
  • shouldComponentUpdate

    判断是否需要更新组件(在首次渲染期间或者调用了forceUpdate方法后,该方法不会被调用)
  • componentWillUpdate

    更新渲染前调用
  • render

    渲染函数,返回DOM结构
  • componentDidUpdate

    更新渲染后调用

销毁

  • componentWillUnmount

    组件移除之前调用

那么这个组件到底是怎么用原生组件渲染的呢?首先我们先来看看最主要的render做了什么。jsx不太直观,我们先翻译一下render:

render() {
let { width, height } = this.props;
return (React.createElement(View, { style: [Styles.panel, {
width: width,
height: height,
}] },
React.createElement(View, { style: Styles.picBlock },
React.createElement(Image, { source: Styles.picUrl })),
React.createElement(View, { style: Styles.textBlock },
React.createElement(Text, { style: Styles.text }, "\u4F60\u7684\u7F51\u7EDC\u597D\u50CF\u4E0D\u7ED9\u529B"),
React.createElement(Text, { style: Styles.text }, "\u70B9\u51FB\u6309\u94AE\u5237\u65B0")),
React.createElement(TouchableOpacity, { style: Styles.button, onPress: this.props.onClickRefresh },
React.createElement(Text, { style: Styles.buttonText }, "\u5237\u65B0"))));
}

这下清晰多了吧?

React.createElement的方法签名:

ReactElement.createElement = function (type, config, children){ ... }

ReactNative的UI组件通过requireNativeComponent -> createReactNativeComponentClass -> ReactNativeBaseComponent下mountComponent的调用关系,最终在mountComponent中调用UIManager组件创建View:UIManager.createView(tag, this.viewConfig.uiViewClassName, nativeTopRootTag, updatePayload);,在Native端,UIManager调用对应组件类型的ViewManager(单例,管理类)创建实例。

*UIManager是一个NativeModule,待下面分析

接下来我们来详细分析下原生组件的实现方法,以Image组件为例:

iOS和Android实现有一定差异,首先是Image组件JS端代码,都需要requireNativeComponent加载原生组件:

const RCTImageView = requireNativeComponent('RCTImageView', Image);

Image的JS端实际上也是一个React JS组件,他也有render,返回的是:(iOS)

<RCTImageView
{...this.props}
style={style}
resizeMode={resizeMode}
tintColor={tintColor}
source={sources}
/>

因为业务逻辑是写在JS端的,创建出了Native组件就需要进行控制,自然就涉及到属性传递、方法调用、事件回调这3个需求。

Native组件跟JS端通讯方式

JS端组件跟Native真正实现的组件主要涉及三件事:

  • 属性同步
  • JS端调用Native方法
  • Native事件回调JS端

属性同步

属性同步很简单,实际上是在组件重新render的时候调用ReactNativeBaseComponentreceiveComponent -> UIManager.updateView完成的。

JS端调用Native方法

两种方法,一种是调用NativeModules(后面有简单分析),如果想直接调用一个具体View的方法,那就需要使用UIManager模块:

Android端UIManager中的定义:

  @ReactMethod
public void dispatchViewManagerCommand(int reactTag, int commandId, ReadableArray commandArgs) {
mUIImplementation.dispatchViewManagerCommand(reactTag, commandId, commandArgs);
}

iOS端UIManager中的定义:

RCT_EXPORT_METHOD(dispatchViewManagerCommand:(nonnull NSNumber *)reactTag
commandID:(NSInteger)commandID
commandArgs:(NSArray<id> *)commandArgs)
{
RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
RCTComponentData *componentData = _componentDataByName[shadowView.viewName];
Class managerClass = componentData.managerClass;
RCTModuleData *moduleData = [_bridge moduleDataForName:RCTBridgeModuleNameForClass(managerClass)];
id<RCTBridgeMethod> method = moduleData.methods[commandID]; NSArray *args = [@[reactTag] arrayByAddingObjectsFromArray:commandArgs];
[method invokeWithBridge:_bridge module:componentData.manager arguments:args];
}

这个方法是从端上映射到JS的,所以在JS端可以这样调用:

UIManager.dispatchViewManagerCommand(
findNodeHandle(this), // 找到与NativeUI组件对应的JS组件实例
UIManager.[UI组件名].Commands.[方法],
[] // 参数
)

findNodeHandle方法是在React中定义,可以找到组件实例的reactTag(执行在JS端),UIManager可以把调用命令分发到Native端对应的组件类型的ViewManager,再通过ViewManager调用View组件实例的对应方法。

Native事件回调JS端

Android端使用的是类似JS端调用Native的方式,使用了事件机制,不过事件的接收者是从JS端映射过来的,React下ReactNativeEventEmitter.receiveEvent(tag, topLevelType, nativeEventParam),所以需要先实现一个Event:(Switch的onValueChange事件)

class ReactSwitchEvent extends Event<ReactSwitchEvent> {
public static final String EVENT_NAME = "topChange"; // topChange会被映射成onChange,具体映射关系参见 UIManagerModuleConstants.java public ReactSwitchEvent(int viewId, boolean isChecked) {
super(viewId);
mIsChecked = isChecked;
} public boolean getIsChecked() {
return mIsChecked;
} @Override
public String getEventName() {
return EVENT_NAME;
} @Override
public short getCoalescingKey() {
// All switch events for a given view can be coalesced.
return 0;
} @Override
public void dispatch(RCTEventEmitter rctEventEmitter) {
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
} private WritableMap serializeEventData() {
WritableMap eventData = Arguments.createMap();
eventData.putInt("target", getViewTag());
eventData.putBoolean("value", getIsChecked());
return eventData;
}
}

然后在ViewManager或View中进行事件派发:

ReactContext reactContext = (ReactContext) buttonView.getContext();
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(
new ReactSwitchEvent(
buttonView.getId(),
isChecked));

iOS端实现有所区别,iOS端将JS函数直接映射到Native,所以可以直接调用(可多次调用):(View为RCTSwitch)

// ViewManager中声明事件为RCTBubblingEventBlock或RCTDirectEventBlock
RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock); // View中声明
@property (nonatomic, copy) RCTBubblingEventBlock onChange; // view实例化时监听onChange
- (void)onChange:(RCTSwitch *)sender
{
if (sender.wasOn != sender.on) {
if (sender.onChange) {
sender.onChange(@{ @"value": @(sender.on) });
}
sender.wasOn = sender.on;
}
}

这样就可以从JS端创建NativeUI组件了,可以看到UI组件的Native和JS端是通过reactTag进行的关联,通过UIManager模块,在Native端的DOM和React的DOM进行同步操作,保持结构一致。

UIManager

模块数据结构,JS端可访问:

UIManager.[UI组件名].[Constants(静态值)/Commands(命令/方法)]

从端上映射的方法:(部分)

  • createView(int tag, String className, int rootViewTag, ReadableMap props)

    创建View
  • updateView(int tag, String className, ReadableMap props)

    更新View
  • manageChildren(int viewTag, Array moveFrom, Array moveTo, Array addChildTags, Array addAtIndices, Array removeFrom)

    批量添加/删除/移动一个view下面的view
  • measure(int reactTag, Callback callback)

    测量View的位置、size等,结果异步回调
  • measureInWindow(int reactTag, Callback callback)

    测量View相对屏幕的位置、size等,结果异步回调
  • dispatchViewManagerCommand(int reactTag, int commandId, ReadableArray commandArgs)

    派发View命令,也就是用来调用对应View的方法

这个模块是NativeModule方式定义的,在RN的JS端启动时,端上会通过JSC把收集到的模块信息(名称)打到JS端全局变量global.__fbBatchedBridgeConfig中,并采用延迟加载策略:设置NativeModules.[模块名]的getter,延迟通过JSC读取模块详细信息(方法、命令号等信息)。在调用的时候会放到MessageQueue的队列里,批量提交,两次批量提交限制的最小间隔为5ms。

关于React Native通讯更详尽的分析参见:React Native通讯原理

React-Native 渲染实现分析的更多相关文章

  1. React Native 从入门到原理

    React Native 是最近非常火的一个话题,介绍如何利用 React Native 进行开发的文章和书籍多如牛毛,但面向入门水平并介绍它工作原理的文章却寥寥无几. 本文分为两个部分:上半部分用通 ...

  2. 关于React Native 火热的话题,从入门到原理

    本文授权转载,作者:bestswifter(简书) React Native 是最近非常火的一个话题,介绍如何利用 React Native 进行开发的文章和书籍多如牛毛,但面向入门水平并介绍它工作原 ...

  3. React Native 从入门到原理一

    React Native 从入门到原理一 React Native 是最近非常火的一个话题,介绍如何利用 React Native 进行开发的文章和书籍多如牛毛,但面向入门水平并介绍它工作原理的文章却 ...

  4. React Native 入门到原理(详解)

    抛砖引玉(帮你更好的去理解怎么产生的 能做什么) 砖一.动态配置 由于 AppStore 审核周期的限制,如何动态的更改 app 成为了永恒的话题.无论采用何种方式,我们的流程总是可以归结为以下三部曲 ...

  5. 《React Native 精解与实战》书籍连载「React Native 底层原理」

    此文是我的出版书籍<React Native 精解与实战>连载分享,此书由机械工业出版社出版,书中详解了 React Native 框架底层原理.React Native 组件布局.组件与 ...

  6. 腾讯优测优分享 | 探索react native首屏渲染最佳实践

    腾讯优测是专业的移动云测试平台,旗下的优分享不定时提供大量移动研发及测试相关的干货~ 此文主要与以下内容相关,希望对大家有帮助. react native给了我们使用javascript开发原生app ...

  7. 探索react native首屏渲染最佳实践

    文 / 腾讯 龚麒 0.前言 react native给了我们使用javascript开发原生app的能力,在使用react native完成兴趣部落安卓端发现tab改造后,我们开始对由react n ...

  8. H5、React Native、Native应用对比分析

    每日更新关注:http://weibo.com/hanjunqiang  新浪微博!iOS开发者交流QQ群: 446310206 "存在即合理".凡是存在的,都是合乎规律的.任何新 ...

  9. Hybrid APP基础篇(二)->Native、Hybrid、React Native、Web App方案的分析比较

    说明 Native.Hybrid.React.Web App方案的分析比较 目录 前言 参考来源 前置技术要求 楔子 几种APP开发模式 概述 Native App Web App Hybrid Ap ...

随机推荐

  1. .net点选验证码实现思路分享

    哈哈好久没冒泡了,最进看见点选验证码有点意思,所以想自己写一个. 先上效果图 如果你被这个效果吸引了就请继续看下去. 贴代码前先说点思路: 1.要有一个汉字库,并按字形分类.(我在数据库里是安部首分类 ...

  2. ExtJS 4.2 介绍

    本篇介绍ExtJS相关知识,是以ExtJS4.2.1版本为基础进行说明,包括:ExtJS的特点.MVC模式.4.2.1GPL版本资源的下载和说明以及4种主题的演示. 目录 1. 介绍 1.1 说明 1 ...

  3. UWP开发必备以及常用知识点总结

    一直在学UWP,一直在写Code,自己到达了什么水平?还有多少东西需要学习才能独挡一面?我想对刚接触UWP的开发者都有这种困惑,偶尔停下来总结分析一下还是很有收获的! 以下内容是自己开发中经常遇到的一 ...

  4. # PHP - 使用PHPMailer发邮件

    PHPMailer支持多种邮件发送方式,使用起来非常简单 1.下载PHPMailer https://github.com/PHPMailer/PHPMailer,下载完成加压后, 把下边的两个文件复 ...

  5. 【热门技术】EventBus 3.0,让事件订阅更简单,从此告别组件消息传递烦恼~

    一.写在前面 还在为时间接收而烦恼吗?还在为各种组件间的消息传递烦恼吗?EventBus 3.0,专注于android的发布.订阅事件总线,让各组件间的消息传递更简单!完美替代Intent,Handl ...

  6. java中Action层、Service层和Dao层的功能区分

    Action/Service/DAO简介: Action是管理业务(Service)调度和管理跳转的. Service是管理具体的功能的. Action只负责管理,而Service负责实施. DAO只 ...

  7. StrategyPattern (策略模式)

    /** * 策略模式 * @author TMAC-J * 根据环境的不同选择不同的策略,把策略用接口抽象出来 */ public class StrategyPattern { interface ...

  8. BZOJ 1103: [POI2007]大都市meg [DFS序 树状数组]

    1103: [POI2007]大都市meg Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 2221  Solved: 1179[Submit][Sta ...

  9. 我的MYSQL学习心得(十三) 权限管理

    我的MYSQL学习心得(十三) 权限管理 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(四) ...

  10. 通过Mono 在 Heroku 上运行 .NET 应用

    英文原文:Running .NET on Heroku 中文原文:在 Heroku 上运行 .NET 应用 自从加入了Heroku之后,我就想在这个平台上运行.NET程序.现在我很高兴向大家宣布,我们 ...