React Native 从入门到原理

React

我想动态修改一个按钮的文字,需要这样写:

<button type="button" id="button" onclick="onClick()">old button</button>
// 在 JavaScript 中操作 DOM:
<script>
function onClick() {
  document.getElementById('button').innerHTML='new button';
}
</script>

可以看到,在 HTML 和 JavaScript 代码中,id 和 onclick 事件触发的函数必须完全对应,否则就无法正确的响应事件。如果想知道一个 HTML 标签会如何被响应,我们还得跑去 JavaScript 代码中查找,这种原始的配置方式很不方便。

于是FaceBook 推出了 React 框架,这个问题得到了大幅度改善。我们可以把一组相关的 HTML 标签,也就是 app 内的 UI 控件,封装进一个组件(Component)中。阮一峰的 React 教程中摘录的一段代码:

var MyComponent = React.createClass({
  handleClick: function() {
    this.refs.myTextInput.focus();
  },
  render: function() {
    return (
      <div>
        <input type="text" ref="myTextInput" />
        <input type="button" value="Focus the text input" onClick={this.handleClick} />
      </div>
    );
  }
});

如果你想问:“为什么 JavaScript 代码里面出现了 HTML 的语法”,那么恭喜你已经初步体会到 React 的奥妙了。这种语法被称为 JSX,它是一种 JavaScript 语法拓展。JSX 允许我们写 HTML 标签或 React 标签,它们终将被转换成原生的 JavaScript 并创建 DOM。

在 React 框架中,除了可以用 JavaScript 写 HTML 以外,我们甚至可以写 CSS,这在后面的例子中可以看到。

React 是一套可以用 简洁 的语法 高效 绘制 DOM 的框架:

简洁因为我们可以暂时放下 HTML 和 CSS,只关心如何用 JavaScript 构造页面

高效是因为 React 独创了 Virtual DOM 机制。Virtual DOM 是一个存在于内存中的 JavaScript 对象,
它与 DOM 是一一对应的关系,也就是说只要有 Virtual DOM,我们就能渲染出 DOM。

当界面发生变化时,得益于高效的 DOM Diff 算法,我们能够知道 Virtual DOM 的变化,从而高效的改动 DOM,避免了重新绘制 DOM。

React 并不是前端开发的全部,它专注于 UI 部分,对应到 MVC 结构中就是 View 层。要想实现完整的 MVC 架构,还需要 Model 和 Controller 的结构。在前端开发时,我们可以采用 Flux 和 Redux 架构,它们并非框架(Library),而是和 MVC 一样都是一种架构设计(Architecture)。

如果不从事前端开发,就不用深入的掌握 Flux 和 Redux 架构,但理解这一套体系结构对于后面理解 React Native 非常重要。

React Native

移动端通过 JSON 文件传递信息,只能传递配置信息,无法表达逻辑。

而 React 在前端取得突破性成功以后,JavaScript 布道者们开始试图一统三端。他们利用了移动平台能够运行 JavaScript 代码的能力,并且发挥了 JavaScript 不仅仅可以传递配置信息,还可以表达逻辑信息的优点。

于是一个基于 JavaScript,具备动态配置能力,面向前端开发者的移动端开发框架,React Native,诞生了!

这是一个面向前端开发者的框架。它的宗旨是让前端开发者像用 React 写网页那样,用 React Native 写移动端应用。

原理概述

  • React Native 与 Hybrid 完全没有关系

    • 即使使用了 React Native,我们依然需要 UIKit 等框架,调用的是 Objective-C 代码。React Native只不过是以 JavaScript 的形式告诉 Objective-C 该执行什么代码。
  • React Native 能够运行起来,全靠 Objective-C 和 JavaScript 的交互。

    • 我们知道 C 系列的语言,经过编译,链接等操作后,会得到一个二进制格式的可执行文,所谓的运行程序,其实是运行这个二进制程序。

    • 而 JavaScript 是一种脚本语言,它不会经过编译、链接等操作,而是在运行时才动态的进行词法、语法分析,生成抽象语法树(AST)和字节码,然后由解释器负责执行或者使用 JIT 将字节码转化为机器码再执行。整个流程由 JavaScript 引擎负责完成。

    • JavaScript 是一种单线程的语言;

    • JavaScript不具备自运行的能力,因此总是被动调用。

    • “JavaScript 线程” 实际上表示的是 Objective-C 创建了一个单独的线程,这个线程只用于执行 JavaScript 代码,而且 JavaScript 代码只会在这个线程中执行。

  • 苹果提供了一个叫做 JavaScript Core 的框架,这是一个 JavaScript 引擎:

    • JSContext 指的是 JavaScript 代码的运行环境,通过方法 evaluateScript 即可执行 JavaScript 代码获取返回结果
// Objective-C 如何调用 JavaScript :
JSContext *context = [[JSContext alloc] init];
JSValue *jsVal = [context evaluateScript:@"21+7"];
int iVal = [jsVal toInt32];

Objective-C 与 JavaScript 交互

Objective-C 和 JavaScript 的交互总是由前者发起

由于 JavaScript Core 是一个面向 Objective-C 的框架,在 Objective-C 这一端,我们对 JavaScript 上下文知根知底,可以很容易的获取到对象,方法等各种信息,当然也包括调用 JavaScript 函数。

真正复杂的问题在于,JavaScript 不知道 Objective-C 有哪些方法可以调用。

  • React Native 解决这个问题的方案是在 Objective-C 和 JavaScript 两端都保存了一份配置表,里面标记了所有 Objective-C 暴露给 JavaScript 的 模块和方法

  • 无论是哪一方调用另一方的方法,实际上传递的数据只有 ModuleId、MethodId 和 Arguments 这三个元素,它们分别表示 类、方法和方法参数

  • JavaScript 解析出将调用方法的三个元素后放入到 MessageQueue 中,等待 Objective-C 拿走

  • 当 Objective-C 接收到这三个元素后,就可以通过 runtime 唯一确定要调用的是哪个Objective-C函数,然后调用这个函数

上述解决方案只是一个抽象概念,可能与实际的解决方案有微小差异,比如实际上 Objective-C 这一端,并没有直接保存这个模块配置表。具体实现将在下一节中随着源码一起分析。

React Native Objective-C端源码分析

配置表的形成 (Objective-C 调用 JavaScript)

每个项目都有一个入口,然后进行初始化操作,React Native 也不例外。一个不含 Objective-C 代码的项目留给我们的唯一线索就是位于 AppDelegate 文件中的代码:

RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName:@"PropertyFinder" initialProperties:nil launchOptions:launchOptions];

用户能看到的一切内容都来源于这个 rootView,所有的初始化工作也都在这个方法内完成。

在这个方法内部,在创建 rootView 之前,React Native 实际上先创建了一个 BatchedBridge 对象。它是 Objective-C 与 JavaScript 交互的桥梁,后续的方法交互完全依赖于它,而整个初始化过程的最终目的其实也就是创建这个桥梁对象。

初始化方法的核心是 setUp 方法,而 setUp 方法的主要任务则是创建 BatchedBridge对象

BatchedBridge对象的作用是批量读取 JavaScript 对 Objective-C 的方法调用,同时它内部持有一个 RCTJSCExecutor对象 对象,用来执行 JavaScript 代码。

创建 BatchedBridge对象 的关键是 start 方法,start方法又分为五个步骤:

1.读取JavaScript源码
2.初始化模块信息
3.初始化 JavaScript 代码的执行器(即 RCTJSCExecutor对象)
4.生成模块列表并写入 JavaScript 端
5.执行 JavaScript 源码

方法调用

如前文所述,在 React Native 中,Objective-C 和 JavaScript 的交互都是通过传递 ModuleId、MethodId 和 Arguments 进行的。以下是分情况讨论:

调用 JavaScript 方法

调用 JavaScript 代码的核心代码如下:

// 这个函数是我们要调用 JavaScript 的中转函数。也就是说它的作用其实是处理参数,而非真正要调用的 JavaScript 函数。
// 这个中转函数接收到的参数包含了 ModuleId、MethodId 和 Arguments,然后由中转函数查找自己的模块配置表,找到真正要调用的 JavaScript 函数
- (void)_executeJSCall:(NSString *)method arguments:(NSArray *)arguments callback:(RCTJavaScriptCallback)onComplete {
    [self executeBlockOnJavaScriptQueue:^{
        // 获取 contextJSRef、methodJSRef、moduleJSRef
        resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)methodJSRef, (JSObjectRef)moduleJSRef, arguments.count, jsArgs, &errorJSRef);
        objcValue = /*resultJSRef 转换成 Objective-C 类型*/
        onComplete(objcValue, nil);
    }];
}

在实际使用的时候,我们可以这样发起对 JavaScript 的调用:

// Name 和 Body 参数分别表示要调用的 JavaScript 的函数名和参数
[_bridge.eventDispatcher sendAppEventWithName:@"greeted"
                                         body:@{ @"name": @"nmae"}];

调用 Objective-C方法

  • JavaScript 解析出方法的 ModuleId、MethodId 和 Arguments 并放入到 MessageQueue 中,等待 Objective-C 拿走 (或者超时后主动发送给 Objective-C)。

  • Objective-C通过查找模块配置表找出要调用的方法,并通过 runtime 动态的调用

    • Objective-C 负责处理调用的方法是 handleBuffer,它的参数是一个含有四个元素的数组,每个元素也都是一个数组,分别存放了 ModuleId、MethodId、Params,第四个元素目测用处不大。

    • 函数内部在每一次方法调用中调用 _handleRequestNumber:moduleID:methodID:params 方法。通过查找模块配置表找出要调用的方法,并通过 runtime 动态的调用:
      [method invokeWithBridge:self module:moduleData.instance arguments:params];

    • 在这个方法中,有一个很关键的方法:processMethodSignature,它会根据 JavaScript 的 CallbackId 创建一个 Block,并且在调用完函数后执行这个 Block。

JavaScript闭包的回调

既然说到函数互调,那么就不得不提到回调了。对于 Objective-C 来说,执行完 JavaScript 代码再执行 Objective-C 回调毫无难度,难点依然在于 JavaScript 代码调用 Objective-C 之后,如何在 Objective-C 的代码中,回调执行 JavaScript 代码。

目前 React Native 的做法是:在 JavaScript 调用 Objective-C 代码时,注册要回调的 Block,并且把 BlockId 作为参数发送给 Objective-C,Objective-C 收到参数时会创建 Block,调用完 Objective-C 函数后就会执行这个刚刚创建的 Block。

Objective-C 会向 Block 中传入参数和 BlockId,然后在 Block 内部调用 JavaScript 的方法,随后 JavaScript 查找到当时注册的 Block 并执行

实战举例

  • 演示 Objective-C 是如何与 JavaScript 进行交互的
// .h 文件
#import <Foundation/Foundation.h>
#import "RCTBridgeModule.h"
@interface Person : NSObject<RCTBridgeModule, RCTBridgeMethod>
@end

// .m 文件
#import "Person.h"
#import "RCTEventDispatcher.h"
#import "RCTConvert.h"
@implementation Person
@synthesize bridge = _bridge;
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(greet:(NSString *)name)
{
  NSLog(@"Hi, %@!", name);
  [_bridge.eventDispatcher sendAppEventWithName:@"greeted"
                                           body:@{ @"name": @"nmae"}];
}
RCT_EXPORT_METHOD(greetss:(NSString *)name name2:(NSString *)name2 callback:(RCTResponseSenderBlock)callback)
{
  NSLog(@"Hi, %@! %@!!!", name, name2);
  callback(@[@[@12,@23,@34]]);
}
@end

// JavaScript 中:
Person.greet('Tadeu');
Person.greetss('Haha', 'Heihei', (events) => {
  for (var i = 0; i < events.length; i++) {
    console.log(events[i]);
  }
});

React Native 优缺点

优点:

1.复用了 React 的思想,有利于前端开发者涉足移动端

2.能够利用 JavaScript 动态更新的特性,快速迭代

3.相比于原生平台,开发速度更快,相比于 Hybrid 框架,性能更好

缺点

1. 开发者依然要为 iOS 和 Android 两个平台提供两套不同的代。有组件是区分平台的,即使是共用组件,也会有平台独享的函数。

2.  不能做到完全屏蔽 iOS 端或 Android端,前端开发者必须对原生平台有所了解。

3.  由于 Objective-C 与 JavaScript 之间的切换存在固定的时间开销,所以性能必定不及原生。(比如目前的官方版本无法做到 UItableview(ListView) 的视图重用,因为滑动过程中,视图重用需要在异步线程中执行,速度太慢。这也就导致随着 Cell 数量的增加,占用的内存也线性增加。)

React Native 交互原理总结

Objective-C 有 `JavaScript Core` 框架用来执行 JavaScript 代码。
JavaScript 通过配置表生成类,方法,参数三个元素,放入消息队列,Objective-C获取之后,
就可以唯一确定要调用的是哪个Objective-C函数,然后调用

参考资料
React Native 从入门到原理-bestswifter

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260

推荐阅读更多精彩内容