React
我想動態(tài)修改一個按鈕的文字末贾,需要這樣寫:
<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 事件觸發(fā)的函數(shù)必須完全對應并村,否則就無法正確的響應事件屿愚。如果想知道一個 HTML 標簽會如何被響應携茂,我們還得跑去 JavaScript 代碼中查找澜搅,這種原始的配置方式很不方便敞掘。
于是FaceBook 推出了 React 框架屿讽,這個問題得到了大幅度改善昭灵。我們可以把一組相關的 HTML 標簽,也就是 app 內(nèi)的 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 代碼里面出現(xiàn)了 HTML 的語法”,那么恭喜你已經(jīng)初步體會到 React 的奧妙了诵棵。這種語法被稱為 JSX
抠蚣,它是一種 JavaScript 語法拓展。JSX 允許我們寫 HTML 標簽或 React 標簽履澳,它們終將被轉換成原生的 JavaScript 并創(chuàng)建 DOM嘶窄。
在 React 框架中,除了可以用 JavaScript 寫 HTML 以外距贷,我們甚至可以寫 CSS柄冲,這在后面的例子中可以看到。
React 是一套可以用 簡潔 的語法 高效 繪制 DOM 的框架:
簡潔因為我們可以暫時放下 HTML 和 CSS忠蝗,只關心如何用 JavaScript 構造頁面
高效是因為 React 獨創(chuàng)了 Virtual DOM 機制现横。Virtual DOM 是一個存在于內(nèi)存中的 JavaScript 對象,
它與 DOM 是一一對應的關系,也就是說只要有 Virtual DOM戒祠,我們就能渲染出 DOM骇两。
當界面發(fā)生變化時,得益于高效的 DOM Diff 算法姜盈,我們能夠知道 Virtual DOM 的變化低千,從而高效的改動 DOM,避免了重新繪制 DOM馏颂。
React 并不是前端開發(fā)的全部示血,它專注于 UI 部分,對應到 MVC 結構中就是 View 層救拉。要想實現(xiàn)完整的 MVC 架構矾芙,還需要 Model 和 Controller 的結構。在前端開發(fā)時近上,我們可以采用 Flux 和 Redux 架構,它們并非框架(Library)拂铡,而是和 MVC 一樣都是一種架構設計(Architecture)壹无。
如果不從事前端開發(fā),就不用深入的掌握 Flux 和 Redux 架構感帅,但理解這一套體系結構對于后面理解 React Native 非常重要斗锭。
React Native
移動端通過 JSON 文件傳遞信息,只能傳遞配置信息失球,無法表達邏輯岖是。
而 React 在前端取得突破性成功以后,JavaScript 布道者們開始試圖一統(tǒng)三端实苞。他們利用了移動平臺能夠運行 JavaScript 代碼的能力豺撑,并且發(fā)揮了 JavaScript 不僅僅可以傳遞配置信息,還可以表達邏輯信息的優(yōu)點黔牵。
于是一個基于 JavaScript聪轿,具備動態(tài)配置能力,面向前端開發(fā)者的移動端開發(fā)框架猾浦,React Native陆错,誕生了!
這是一個面向前端開發(fā)者的框架金赦。它的宗旨是讓前端開發(fā)者像用 React 寫網(wǎng)頁那樣音瓷,用 React Native 寫移動端應用。
原理概述
-
React Native 與 Hybrid 完全沒有關系
- 即使使用了 React Native夹抗,我們依然需要 UIKit 等框架绳慎,調(diào)用的是 Objective-C 代碼。React Native只不過是以 JavaScript 的形式告訴 Objective-C 該執(zhí)行什么代碼。
-
React Native 能夠運行起來偷线,全靠 Objective-C 和 JavaScript 的交互磨确。
我們知道 C 系列的語言,經(jīng)過編譯声邦,鏈接等操作后乏奥,會得到一個二進制格式的可執(zhí)行文,所謂的運行程序亥曹,其實是運行這個二進制程序邓了。
而 JavaScript 是一種腳本語言,它不會經(jīng)過編譯媳瞪、鏈接等操作骗炉,而是在運行時才動態(tài)的進行詞法、語法分析蛇受,生成抽象語法樹(AST)和字節(jié)碼句葵,然后由解釋器負責執(zhí)行或者使用 JIT 將字節(jié)碼轉化為機器碼再執(zhí)行。整個流程由 JavaScript 引擎負責完成兢仰。
JavaScript 是一種單線程的語言乍丈;
JavaScript不具備自運行的能力,因此總是被動調(diào)用把将。
“JavaScript 線程” 實際上表示的是 Objective-C 創(chuàng)建了一個單獨的線程轻专,這個線程只用于執(zhí)行 JavaScript 代碼,而且 JavaScript 代碼只會在這個線程中執(zhí)行察蹲。
-
蘋果提供了一個叫做 JavaScript Core 的框架请垛,這是一個 JavaScript 引擎:
- JSContext 指的是 JavaScript 代碼的運行環(huán)境,通過方法
evaluateScript
即可執(zhí)行 JavaScript 代碼獲取返回結果
- JSContext 指的是 JavaScript 代碼的運行環(huán)境,通過方法
// Objective-C 如何調(diào)用 JavaScript :
JSContext *context = [[JSContext alloc] init];
JSValue *jsVal = [context evaluateScript:@"21+7"];
int iVal = [jsVal toInt32];
Objective-C 與 JavaScript 交互
Objective-C 和 JavaScript 的交互總是由前者發(fā)起
由于 JavaScript Core 是一個面向 Objective-C 的框架洽议,在 Objective-C 這一端宗收,我們對 JavaScript 上下文知根知底,可以很容易的獲取到對象亚兄,方法等各種信息镜雨,當然也包括調(diào)用 JavaScript 函數(shù)。
真正復雜的問題在于儿捧,JavaScript 不知道 Objective-C 有哪些方法可以調(diào)用荚坞。
React Native 解決這個問題的方案是在 Objective-C 和 JavaScript 兩端都保存了一份配置表,里面標記了所有 Objective-C 暴露給 JavaScript 的 模塊和方法菲盾。
無論是哪一方調(diào)用另一方的方法颓影,實際上傳遞的數(shù)據(jù)只有 ModuleId、MethodId 和 Arguments 這三個元素懒鉴,它們分別表示 類诡挂、方法和方法參數(shù)
JavaScript 解析出將調(diào)用方法的三個元素后放入到 MessageQueue 中碎浇,等待 Objective-C 拿走
當 Objective-C 接收到這三個元素后,就可以通過 runtime 唯一確定要調(diào)用的是哪個Objective-C函數(shù)璃俗,然后調(diào)用這個函數(shù)
上述解決方案只是一個抽象概念奴璃,可能與實際的解決方案有微小差異,比如實際上 Objective-C 這一端城豁,并沒有直接保存這個模塊配置表苟穆。具體實現(xiàn)將在下一節(jié)中隨著源碼一起分析。
React Native Objective-C端源碼分析
配置表的形成 (Objective-C 調(diào)用 JavaScript)
每個項目都有一個入口唱星,然后進行初始化操作雳旅,React Native 也不例外。一個不含 Objective-C 代碼的項目留給我們的唯一線索就是位于 AppDelegate 文件中的代碼:
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName:@"PropertyFinder" initialProperties:nil launchOptions:launchOptions];
用戶能看到的一切內(nèi)容都來源于這個 rootView间聊,所有的初始化工作也都在這個方法內(nèi)完成攒盈。
在這個方法內(nèi)部,在創(chuàng)建 rootView 之前哎榴,React Native 實際上先創(chuàng)建了一個 BatchedBridge 對象
型豁。它是 Objective-C 與 JavaScript 交互的橋梁,后續(xù)的方法交互完全依賴于它尚蝌,而整個初始化過程的最終目的其實也就是創(chuàng)建這個橋梁對象偷遗。
初始化方法的核心是 setUp 方法,而 setUp 方法的主要任務則是創(chuàng)建 BatchedBridge對象
驼壶。
BatchedBridge對象
的作用是批量讀取 JavaScript 對 Objective-C 的方法調(diào)用,同時它內(nèi)部持有一個 RCTJSCExecutor對象 對象
喉酌,用來執(zhí)行 JavaScript 代碼热凹。
創(chuàng)建 BatchedBridge對象
的關鍵是 start 方法,start方法又分為五個步驟:
1.讀取JavaScript源碼
2.初始化模塊信息
3.初始化 JavaScript 代碼的執(zhí)行器(即 RCTJSCExecutor對象)
4.生成模塊列表并寫入 JavaScript 端
5.執(zhí)行 JavaScript 源碼
方法調(diào)用
如前文所述泪电,在 React Native 中般妙,Objective-C 和 JavaScript 的交互都是通過傳遞 ModuleId、MethodId 和 Arguments 進行的相速。以下是分情況討論:
調(diào)用 JavaScript 方法
調(diào)用 JavaScript 代碼的核心代碼如下:
// 這個函數(shù)是我們要調(diào)用 JavaScript 的中轉函數(shù)碟渺。也就是說它的作用其實是處理參數(shù),而非真正要調(diào)用的 JavaScript 函數(shù)突诬。
// 這個中轉函數(shù)接收到的參數(shù)包含了 ModuleId苫拍、MethodId 和 Arguments,然后由中轉函數(shù)查找自己的模塊配置表旺隙,找到真正要調(diào)用的 JavaScript 函數(shù)
- (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);
}];
}
在實際使用的時候蔬捷,我們可以這樣發(fā)起對 JavaScript 的調(diào)用:
// Name 和 Body 參數(shù)分別表示要調(diào)用的 JavaScript 的函數(shù)名和參數(shù)
[_bridge.eventDispatcher sendAppEventWithName:@"greeted"
body:@{ @"name": @"nmae"}];
調(diào)用 Objective-C方法
JavaScript 解析出方法的 ModuleId垄提、MethodId 和 Arguments 并放入到 MessageQueue 中榔袋,等待 Objective-C 拿走 (或者超時后主動發(fā)送給 Objective-C)。
-
Objective-C通過查找模塊配置表找出要調(diào)用的方法铡俐,并通過 runtime 動態(tài)的調(diào)用
Objective-C 負責處理調(diào)用的方法是
handleBuffer
凰兑,它的參數(shù)是一個含有四個元素的數(shù)組,每個元素也都是一個數(shù)組审丘,分別存放了 ModuleId吏够、MethodId、Params备恤,第四個元素目測用處不大稿饰。函數(shù)內(nèi)部在每一次方法調(diào)用中調(diào)用
_handleRequestNumber:moduleID:methodID:params
方法。通過查找模塊配置表找出要調(diào)用的方法露泊,并通過 runtime 動態(tài)的調(diào)用:
[method invokeWithBridge:self module:moduleData.instance arguments:params];
在這個方法中喉镰,有一個很關鍵的方法:
processMethodSignature
,它會根據(jù) JavaScript 的 CallbackId 創(chuàng)建一個 Block惭笑,并且在調(diào)用完函數(shù)后執(zhí)行這個 Block侣姆。
JavaScript閉包的回調(diào)
既然說到函數(shù)互調(diào),那么就不得不提到回調(diào)了沉噩。對于 Objective-C 來說捺宗,執(zhí)行完 JavaScript 代碼再執(zhí)行 Objective-C 回調(diào)毫無難度,難點依然在于 JavaScript 代碼調(diào)用 Objective-C 之后川蒙,如何在 Objective-C 的代碼中蚜厉,回調(diào)執(zhí)行 JavaScript 代碼。
目前 React Native 的做法是:在 JavaScript 調(diào)用 Objective-C 代碼時畜眨,注冊要回調(diào)的 Block昼牛,并且把 BlockId 作為參數(shù)發(fā)送給 Objective-C,Objective-C 收到參數(shù)時會創(chuàng)建 Block康聂,調(diào)用完 Objective-C 函數(shù)后就會執(zhí)行這個剛剛創(chuàng)建的 Block贰健。
Objective-C 會向 Block 中傳入?yún)?shù)和 BlockId,然后在 Block 內(nèi)部調(diào)用 JavaScript 的方法恬汁,隨后 JavaScript 查找到當時注冊的 Block 并執(zhí)行
實戰(zhàn)舉例
- 演示 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 優(yōu)缺點
優(yōu)點:
1.復用了 React 的思想伶椿,有利于前端開發(fā)者涉足移動端
2.能夠利用 JavaScript 動態(tài)更新的特性,快速迭代
3.相比于原生平臺氓侧,開發(fā)速度更快脊另,相比于 Hybrid 框架,性能更好
缺點
1. 開發(fā)者依然要為 iOS 和 Android 兩個平臺提供兩套不同的代约巷。有組件是區(qū)分平臺的尝蠕,即使是共用組件,也會有平臺獨享的函數(shù)载庭。
2. 不能做到完全屏蔽 iOS 端或 Android端看彼,前端開發(fā)者必須對原生平臺有所了解廊佩。
3. 由于 Objective-C 與 JavaScript 之間的切換存在固定的時間開銷,所以性能必定不及原生靖榕。(比如目前的官方版本無法做到 UItableview(ListView) 的視圖重用标锄,因為滑動過程中,視圖重用需要在異步線程中執(zhí)行茁计,速度太慢料皇。這也就導致隨著 Cell 數(shù)量的增加,占用的內(nèi)存也線性增加星压。)
React Native 交互原理總結
Objective-C 有 `JavaScript Core` 框架用來執(zhí)行 JavaScript 代碼践剂。
JavaScript 通過配置表生成類,方法娜膘,參數(shù)三個元素逊脯,放入消息隊列,Objective-C獲取之后竣贪,
就可以唯一確定要調(diào)用的是哪個Objective-C函數(shù)军洼,然后調(diào)用