React Native 從入門到原理

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 代碼獲取返回結果
// 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)用

參考資料
React Native 從入門到原理-bestswifter

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市演怎,隨后出現(xiàn)的幾起案子匕争,更是在濱河造成了極大的恐慌,老刑警劉巖爷耀,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件甘桑,死亡現(xiàn)場離奇詭異,居然都是意外死亡歹叮,警方通過查閱死者的電腦和手機跑杭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來盗胀,“玉大人,你說我怎么就攤上這事锄贼∑被遥” “怎么了?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵宅荤,是天一觀的道長屑迂。 經(jīng)常有香客問我,道長冯键,這世上最難降的妖魔是什么惹盼? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮惫确,結果婚禮上手报,老公的妹妹穿的比我還像新娘蚯舱。我一直安慰自己,他們只是感情好掩蛤,可當我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布枉昏。 她就那樣靜靜地躺著,像睡著了一般揍鸟。 火紅的嫁衣襯著肌膚如雪兄裂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天阳藻,我揣著相機與錄音晰奖,去河邊找鬼。 笑死腥泥,一個胖子當著我的面吹牛匾南,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播道川,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼午衰,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了冒萄?” 一聲冷哼從身側響起臊岸,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎尊流,沒想到半個月后帅戒,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡崖技,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年逻住,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片迎献。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡瞎访,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出吁恍,到底是詐尸還是另有隱情扒秸,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布冀瓦,位于F島的核電站伴奥,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏翼闽。R本人自食惡果不足惜拾徙,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望感局。 院中可真熱鬧尼啡,春花似錦暂衡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至读恃,卻和暖如春隧膘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背寺惫。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工疹吃, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人西雀。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓萨驶,卻偏偏與公主長得像,于是被迫代替她去往敵國和親艇肴。 傳聞我的和親對象是個殘疾皇子腔呜,可洞房花燭夜當晚...
    茶點故事閱讀 44,884評論 2 354

推薦閱讀更多精彩內(nèi)容

  • React Native 是最近非常火的一個話題再悼,介紹如何利用 React Native 進行開發(fā)的文章和書籍多如...
    零度_不結冰閱讀 676評論 0 1
  • React Native 是最近非澈顺耄火的一個話題,上次一個遇到了我一個從北京回來的哥們冲九,他也是做iOS的谤草,跟他聊天...
    陀螺旋轉呼啦圈閱讀 565評論 0 1
  • 文/譚靜兒 01 “全體小伙伴們,明天早上八點半在XX酒莊開早會莺奸,請大家準時參加丑孩。” 公司領導又來搞什么事情灭贷?看著...
    譚靜兒閱讀 547評論 0 3
  • 小男孩收集了很多石頭,小女孩有很多的糖果古拴,小男孩想用所有的石頭與小女孩的糖果做個交換箩帚。小女孩同意了真友。 小男孩偷偷地...
    夕夕子月閱讀 274評論 0 0
  • 2016的自己強大到爆黄痪,經(jīng)歷了高考,雖然一路磕磕絆絆盔然,不甚通暢桅打,卻也未辜負自己的野心是嗜。18歲,在你身邊的我以你為生...
    賣花擔上閱讀 246評論 0 1