关闭

React Native你需要整理一下

413人阅读 评论(0) 收藏 举报
分类:

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 的定位是:

利用脚本语言进行原生平台开发的一次成功尝试,降低了前端开发者入门移动端的门槛,一定业务场景下具有独特的优势,几乎不可能取代原生平台开发。

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:100360次
    • 积分:1649
    • 等级:
    • 排名:千里之外
    • 原创:68篇
    • 转载:22篇
    • 译文:3篇
    • 评论:11条