您的位置:首页 > Web前端 > React

React Native你需要整理一下

2016-08-29 00:57 375 查看
React Native 源码分析

要想深入理解 React Native 的工作原理,有两个部分有必要阅读一下,分别是初始化阶段和方法调用阶段。

为了提炼出代码的核心含义,我会在不改变代码意图的基础上对它做一些删改,以便阅读。

写这篇文章是,React Native 还处于 0.27 版本,由于在 1.0 之前的变动幅度相对较大,因此下面的源码分析很可能随着 React Native 的演变而过时。但不管何时,把下面的源码读一遍都有助于你加深对 React Native 原理的理解。

初始化 React Native

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

Objective-C

RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation

moduleName:@”PropertyFinder”

initialProperties:nil

launchOptions:launchOptions];

1

2

3

4

RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation

moduleName:@”PropertyFinder”

initialProperties:nil

launchOptions:launchOptions];

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

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

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

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

创建 BatchedBridge 的关键是 start 方法,它可以分为五个步骤:

读取 JavaScript 源码

初始化模块信息

初始化 JavaScript 代码的执行器,即 RCTJSCExecutor 对象

生成模块列表并写入 JavaScript 端

执行 JavaScript 源码

我们逐个分析每一步完成的操作:

读取 JavaScript 源码

这一部分的具体代码实现没有太大的讨论意义。我们只要明白,JavaScript 的代码是在 Objective-C 提供的环境下运行的,所以第一步就是把 JavaScript 加载进内存中,对于一个空的项目来说,所有的 JavaScript 代码大约占用 1.5 Mb 的内存空间。

需要说明的是,在这一步中,JSX 代码已经被转化成原生的 JavaScript 代码。

初始化模块信息

这一步在方法 initModulesWithDispatchGroup: 中实现,主要任务是找到所有需要暴露给 JavaScript 的类。每一个需要暴露给 JavaScript 的类(也成为 Module,以下不作区分)都会标记一个宏:RCT_EXPORT_MODULE,这个宏的具体实现并不复杂:

Objective-C

define RCT_EXPORT_MODULE(js_name) \

RCT_EXTERN void RCTRegisterModule(Class); \

+ (NSString *)moduleName { return @#js_name; } \

+ (void)load { RCTRegisterModule(self); }

1

2

3

4

define RCT_EXPORT_MODULE(js_name) \

RCT_EXTERN void RCTRegisterModule(Class); \

+ (NSString *)moduleName { return @#js_name; } \

+ (void)load { RCTRegisterModule(self); }

这样,这个类在 load 方法中就会调用 RCTRegisterModule 方法注册自己:

Objective-C

void RCTRegisterModule(Class moduleClass)

{

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

RCTModuleClasses = [NSMutableArray new];

});

[RCTModuleClasses addObject:moduleClass];

}

1

2

3

4

5

6

7

8

9

void RCTRegisterModule(Class moduleClass)

{

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

RCTModuleClasses = [NSMutableArray new];

});

[RCTModuleClasses addObject:moduleClass];

}

因此,React Native 可以通过 RCTModuleClasses 拿到所有暴露给 JavaScript 的类。下一步操作是遍历这个数组,然后生成 RCTModuleData 对象:

Objective-C

for (Class moduleClass in RCTGetModuleClasses()) {

RCTModuleData *moduleData = [[RCTModuleData alloc]initWithModuleClass:moduleClass bridge:self];

[moduleClassesByID addObject:moduleClass];

[moduleDataByID addObject:moduleData];

}

1

2

3

4

5

for (Class moduleClass in RCTGetModuleClasses()) {

RCTModuleData *moduleData = [[RCTModuleData alloc]initWithModuleClass:moduleClass bridge:self];

[moduleClassesByID addObject:moduleClass];

[moduleDataByID addObject:moduleData];

}

可以想见,RCTModuleData 对象是模块配置表的主要组成部分。如果把模块配置表想象成一个数组,那么每一个元素就是一个 RCTModuleData 对象。

这个对象保存了 Module 的名字,常量等基本信息,最重要的属性是一个数组,保存了所有需要暴露给 JavaScript 的方法。

暴露给 JavaScript 的方法需要用 RCT_EXPORT_METHOD 这个宏来标记,它的实现原理比较复杂,有兴趣的读者可以自行阅读。简单来说,它为函数名加上了 rct_export 前缀,再通过 runtime 获取类的函数列表,找出其中带有指定前缀的方法并放入数组中:

Objective-C

(NSArray

import

import “RCTBridgeModule.h”

@interface Person : NSObject

import

import “RCTBridgeModule.h”

@interface Person : NSObject

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

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 中,可以这样调用:

Objective-C

Person.greet(‘Tadeu’);

Person.greetss(‘Haha’, ‘Heihei’, (events) => {

for (var i = 0; i < events.length; i++) {

console.log(events[i]);

}

});

1

2

3

4

5

6

Person.greet(‘Tadeu’);

Person.greetss(‘Haha’, ‘Heihei’, (events) => {

for (var i = 0; i < events.length; i++) {

console.log(events[i]);

}

});

有兴趣的同学可以复制以上代码并自行调试。

React Native 优缺点分析

经过一长篇的讨论,其实 React Native 的优缺点已经不难分析了,这里简单总结一下:

优点

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

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

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

缺点

做不到 Write once, Run everywhere,也就是说开发者依然需要为 iOS 和 Android 平台提供两套不同的代码,比如参考官方文档可以发现不少组件和API都区分了 Android 和 iOS 版本。即使是共用组件,也会有平台独享的函数。

不能做到完全屏蔽 iOS 端或 Android 的细节,前端开发者必须对原生平台有所了解。加重了学习成本。对于移动端开发者来说,完全不具备用 React Native 开发的能力。

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

综上,我对 React Native 的定位是:

利用脚本语言进行原生平台开发的一次成功尝试,降低了前端开发者入门移动端的门槛,一定业务场景下具有独特的优势,几乎不可能取代原生平台开发。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: