安裝入門
安裝入門可以參考:React Native官方文檔氓英。
NodeJS知識(shí)儲(chǔ)備:參考《NodeJS入門》。(尊重知識(shí)鹦筹,請(qǐng)購買原版)铝阐。
書籍:《React Native入門與實(shí)戰(zhàn)》
代碼示例:30天學(xué)習(xí)React Native教程
看到這里,對(duì)React Native的使用就有了一些認(rèn)識(shí)了铐拐。
React and React Native and NodeJS
React是由Facebook開發(fā)出來的用于開發(fā)用戶交互界面的JS庫徘键。其源碼由Facebook和社區(qū)優(yōu)秀的程序員維護(hù)。React帶來了很多新的東西遍蟋,例如組件化吹害、JSX、虛擬DOM等虚青。其提供的虛擬DOM使得我們渲染組件呈現(xiàn)非常之快它呀,讓我們從頻繁操作DOM的繁重工作之中解脫。它做的工作更多偏重于MVC中的V層棒厘,結(jié)合其它如Flux等一起纵穿,你可以非常容易構(gòu)建強(qiáng)大的應(yīng)用。
React的世界里绊谭,一切都是組件政恍。你可以構(gòu)建任何直接的HTML沒有的組件,例如下拉菜單达传、導(dǎo)航菜單等篙耗。同時(shí),組件里也可以包含其它組件宪赶。每一個(gè)組件都有一個(gè)render方法宗弯,用于呈現(xiàn)該組件。同時(shí)搂妻,每一個(gè)組件都有屬于自己的scope蒙保,從而與其它的組件界定開來,用于構(gòu)建屬于該組件的方法欲主,以方便復(fù)用邓厕。JSX是基于JS的擴(kuò)展,它允許你在JS里直接寫HTML的代碼扁瓢,而不用像我們過去一樣要想在JS里寫HTML不得不拼接一大堆的字符串详恼。React不直接操作DOM,頻繁的操作DOM會(huì)非常影響性能和體驗(yàn)引几。React將DOM結(jié)構(gòu)儲(chǔ)存在內(nèi)存中昧互,與render方法的返回值進(jìn)行比較,通過其自由的diff算法計(jì)算出不同的地方,然后反應(yīng)到真實(shí)的DOM當(dāng)中敞掘。也就是說叽掘,大多數(shù)情況我們渲染組件、更改組件狀態(tài)等都是操作的虛擬DOM玖雁,只有在有所改變的情況下更扁,才會(huì)反應(yīng)到真實(shí)的DOM當(dāng)中。React Native基于ReacJS赫冬,把 React 編程模式的能力帶到移動(dòng)開發(fā),用來開發(fā)iOS和Android原生應(yīng)用.
NodeJs 是基于JavaScript的,可以做為后臺(tái)開發(fā)的語言. 提供了很多系統(tǒng)級(jí)的API疯潭,如文件操作、網(wǎng)絡(luò)編程等. 用事件驅(qū)動(dòng), 異步編程,主要是為后臺(tái)網(wǎng)絡(luò)服務(wù)設(shè)計(jì).React Native 借助 Node.js面殖,即 JavaScript 運(yùn)行時(shí)來創(chuàng)建 JavaScript 代碼竖哩。
總結(jié)來說,React Native使用NodeJS來做系統(tǒng)處理脊僚,使用React來渲染相叁。
構(gòu)建原理
在AppDelegate.m里,找到
application:didFinishLaunchingWithOptions:
在這個(gè)方法中辽幌,主要做了幾件事:
- 定義了 JS 代碼所在的位置增淹,它在 dev 環(huán)境下是一個(gè) URL,通過 development server 訪問乌企;在生產(chǎn)環(huán)境下則從磁盤讀取虑润,當(dāng)然前提是已經(jīng)手動(dòng)生成過了 bundle 文件;
- 創(chuàng)建了一個(gè) RCTRootView 對(duì)象加酵,該類繼承于 UIView拳喻,處理程序所有 View 的最外層;
- 調(diào)用 RCTRootView 的 initWithBundleURL 方法猪腕。在該方法中冗澈,創(chuàng)建了 bridge 對(duì)象。顧名思義陋葡,bridge 起著兩個(gè)端之間的橋接作用亚亲,其中真正工作的是類就是大名鼎鼎的 RCTBatchedBridge。RCTBatchedBridge 是初始化時(shí)通信的核心腐缤,我們重點(diǎn)關(guān)注的是 start 方法捌归。在 start 方法中,會(huì)創(chuàng)建一個(gè) GCD 線程岭粤,該線程通過串行隊(duì)列調(diào)度了以下幾個(gè)關(guān)鍵的任務(wù)惜索。
RCTRootView 用于加載 JavaScript 應(yīng)用以及渲染最后的視圖的。當(dāng)應(yīng)用開始運(yùn)行的時(shí)候绍在,RCTRootView將會(huì)從以下的URL中加載應(yīng)用:
http://localhost:8081/index.ios.bundle
重新調(diào)用了你運(yùn)行這個(gè)App時(shí)打開的終端窗口门扇,它開啟了一個(gè) packager 和 server 來處理上面的請(qǐng)求。在 Safari 中打開那個(gè) URL偿渡;你將會(huì)看到這個(gè) App 的 JavaScript 代碼臼寄。你也可以在 React Native 框架中找到你的代碼。當(dāng)你的App開始運(yùn)行了以后溜宽,這段代碼將會(huì)被加載進(jìn)來吉拳,然后 JavaScriptCore 框架將會(huì)執(zhí)行它。在程序里适揉,它將會(huì)加載 功能 組件留攒,然后構(gòu)建出原生的 UIKit 視圖。JavaScript應(yīng)用運(yùn)行在模擬器上嫉嘀,使用的是原生UI炼邀,沒有任何內(nèi)嵌的瀏覽器。應(yīng)用程序會(huì)使用 React.createElement 來構(gòu)建應(yīng)用 UI ,React會(huì)將其轉(zhuǎn)換到原生環(huán)境中剪侮。
當(dāng) UI 渲染出來后,render 方法會(huì)返回一顆視圖渲染樹,并與當(dāng)前的 UIKit 視圖進(jìn)行比較拭宁。這個(gè)稱之為 reconciliation 的過程的輸出是一個(gè)簡單的更新列表, React 會(huì)將這個(gè)列表應(yīng)用到當(dāng)前視圖。只有實(shí)際改變了的部分才會(huì)重新繪制瓣俯。即ReactJS獨(dú)特的——virtual-DOM(文檔對(duì)象模型,一個(gè)web文檔的視圖樹)和 reconciliation概念杰标。
組件的生命周期
組件的生命周期分成三個(gè)狀態(tài):
Mounting:已插入真實(shí) DOM
Updating:正在被重新渲染
Unmounting:已移出真實(shí) DOM
React 為每個(gè)狀態(tài)都提供了兩種處理函數(shù),will 函數(shù)在進(jìn)入狀態(tài)之前調(diào)用彩匕,did 函數(shù)在進(jìn)入狀態(tài)之后調(diào)用腔剂,三種狀態(tài)共計(jì)五種處理函數(shù)。
componentWillMount()
componentDidMount()
componentWillUpdate(object nextProps, object nextState)
componentDidUpdate(object prevProps, object prevState)
componentWillUnmount()
此外驼仪,React 還提供兩種特殊狀態(tài)的處理函數(shù)掸犬。
componentWillReceiveProps(object nextProps):已加載組件收到新的參數(shù)時(shí)調(diào)用
shouldComponentUpdate(object nextProps, object nextState):組件判斷是否重新渲染時(shí)調(diào)用
這些方法的詳細(xì)說明,可以參考官方文檔绪爸。
另外一個(gè)需要關(guān)注的點(diǎn)是登渣,組件的style屬性的設(shè)置方式不能寫成
style="opacity:{this.state.opacity};"
而要寫成
style={{opacity: this.state.opacity}}
這是因?yàn)?React 組件樣式是一個(gè)對(duì)象,所以第一重大括號(hào)表示這是 JavaScript 語法毡泻,第二重大括號(hào)表示樣式對(duì)象胜茧。
JS 和 Native 交互
xcode啟動(dòng)后會(huì)執(zhí)行 ../node_modules/react-native/packager/react-native-xcode.sh文件。腳本中主要是讀取 Xcode 帶過來的環(huán)境變量仇味,同時(shí)加載 nvm 包使得 Node.js 環(huán)境可用呻顽,最后執(zhí)行 react-native-cli 的命令:
$NODE_BINARY "$REACT_NATIVE_DIR/local-cli/cli.js" bundle \\
--entry-file index.ios.js \\
--platform ios \\
--dev $DEV \\
--bundle-output "$DEST/main.jsbundle" \\
--assets-dest "$DEST"
通過此處,index.ios.js和main.jsbundle就可以使用了丹墨。
通過../react-native/local-cli/cli.js 中的 run 方法廊遍,進(jìn)入/bundle/bundle.js ,由此進(jìn)入了 /bundle/buildBundle.js。從js腳本中可以看出大體做了下面的工作:
- 從入口文件開始分析模塊之間的依賴關(guān)系贩挣;
- 對(duì) JS 文件轉(zhuǎn)化喉前,比如 JSX 語法的轉(zhuǎn)化等没酣;
- 把轉(zhuǎn)化后的各個(gè)模塊一起合并為一個(gè) bundle.js。
React Native對(duì)模塊的分析和編譯做了不少優(yōu)化卵迂,大大提升了打包的速度裕便,這樣能夠保證在 liveReload 時(shí)用戶及時(shí)得到響應(yīng)。
在應(yīng)用程序啟動(dòng)之后见咒,其中的 didFinishLaunchingWithOptions 方法會(huì)被調(diào)用偿衰,通過上面的分析,我們可以看到自己實(shí)現(xiàn)的頁面就被加入到應(yīng)用程序中了改览。JS 引擎下翎,在調(diào)試環(huán)境下,對(duì)應(yīng)的 Executor 為 RCTWebSocketExecutor宝当,它通過 WebSocket 連接到 Chrome 中视事,在 Chrome 里運(yùn)行 JS;在生產(chǎn)環(huán)境下,對(duì)應(yīng)的 Executor 為 RCTContextExecutor庆揩,這應(yīng)該就是傳說中的 javascriptcore郑口。
Native 調(diào)用 JS 是通過發(fā)送消息到 Chrome 觸發(fā)執(zhí)行、或者直接通過 javascriptcore 執(zhí)行 JS 代碼的盾鳞。在 JS 端調(diào)用 Native 一般都是直接通過引用模塊名犬性,JS 把(調(diào)用模塊、調(diào)用方法腾仅、調(diào)用參數(shù)) 保存到隊(duì)列中乒裆;Native 調(diào)用 JS 時(shí),順便把隊(duì)列返回過來推励;Native 處理隊(duì)列中的參數(shù)鹤耍,同樣解析出(模塊、方法验辞、參數(shù))稿黄,并通過 NSInvocation 動(dòng)態(tài)調(diào)用;Native方法調(diào)用完畢后跌造,再次主動(dòng)調(diào)用 JS杆怕。JS 端通過 callbackID,找到對(duì)應(yīng)JS端的 callback壳贪,進(jìn)行一次調(diào)用陵珍。兩端都保存了所有暴露的 Native 模塊信息表作為通信的基礎(chǔ)。
JS不會(huì)主動(dòng)傳遞數(shù)據(jù)給OC违施,在調(diào)OC方法時(shí)互纯,會(huì)把ModuleID,MethodID等數(shù)據(jù)加到一個(gè)隊(duì)列里,等OC過來調(diào)JS的任意方法時(shí)磕蒲,再把這個(gè)隊(duì)列返回給OC留潦,此時(shí)OC再執(zhí)行這個(gè)隊(duì)列里要調(diào)用的方法只盹。native開發(fā)里,只在有事件觸發(fā)的時(shí)候才執(zhí)行代碼兔院。在React Native里殖卑,事件發(fā)生時(shí)OC都會(huì)調(diào)用JS相應(yīng)的模塊方法去處理,處理完這些事件后再執(zhí)行JS想讓OC執(zhí)行的方法秆乳,而沒有事件發(fā)生的時(shí)候,是不會(huì)執(zhí)行任何代碼的钻哩。
另外屹堰,一個(gè) Native 模塊如果想要暴露給 JS,需要在聲明時(shí)顯示地調(diào)用 RCT_EXPORT_MODULE街氢。宏定義了 load 方法扯键,該方法會(huì)自動(dòng)被調(diào)用,在方法中對(duì)當(dāng)前類進(jìn)行注冊(cè)珊肃。模塊如果要暴露出指定的方法荣刑,需要通過 RCT_EXPORT_METHOD 宏進(jìn)行聲明。
總結(jié):整個(gè)啟動(dòng)過程就是伦乔,JS端先把代碼大包成bundle.js傳到Native端的主函數(shù)厉亏,主函數(shù)創(chuàng)建RCTRootView.在RCTRootView里使用GCD掃描暴露的模塊,創(chuàng)建JS引擎烈和,將模塊信息序列化為json.此時(shí)加載JS代碼爱只,在JS引擎中執(zhí)行bundle.js,將json對(duì)象反序列化保存為NativeModules對(duì)象。
JS 和 Native 的交互過程中招刹, RCTBatchedBridge 在兩端通信過程中扮演了重要的角色恬试。
//RCTBatchedBridge.m
- (void)start
{
dispatch_queue_t bridgeQueue = dispatch_queue_create("com.facebook.react.RCTBridgeQueue", DISPATCH_QUEUE_CONCURRENT);
// 異步的加載打包完成的js文件,也就是main.jsbundle疯暑,如果包文件在本地則直接加載训柴,否則根據(jù)URL通過NSURLSession方式去下載
[self loadSource:^(NSError *error, NSData *source) {}];
// 同步初始化需要暴露給給js層的native模塊
[self initModules];
//異步初始化JS Executor,也就是js引擎
dispatch_group_async(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
[weakSelf setUpExecutor];
});
//異步獲取各個(gè)模塊的配置信息
dispatch_group_async(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
config = [weakSelf moduleConfig];
});
//獲取各模塊的配置信息后妇拯,將這些信息注入到JS環(huán)境中
[self injectJSONConfiguration:config onComplete:^(NSError *error) {}];
//開始執(zhí)行main.jsbundle
[self executeSourceCode:sourceCode];
}
js為了橋接native層也引入了BatchedBridge幻馁,BatchedBridge是MessageQueue的一個(gè)實(shí)例,而且是全局唯一的一個(gè)實(shí)例越锈。:
//BatchedBridge.js
const MessageQueue = require('MessageQueue');
const BatchedBridge = new MessageQueue(
__fbBatchedBridgeConfig.remoteModuleConfig,
__fbBatchedBridgeConfig.localModulesConfig,
);
//將BatchedBridge添加到j(luò)s的全局global對(duì)象中宣赔,
Object.defineProperty(global, '__fbBatchedBridge', { value: BatchedBridge });
module.exports = BatchedBridge;
__fbBatchedBridgeConfig是一個(gè)全局的js變量,__fbBatchedBridgeConfig.remoteModuleConfig就是之前我們?cè)趎ative層導(dǎo)出的模塊配置表.
messageQueue保存著js跟native的模塊交互的所有信息瞪浸。<code>_genModules</code>方法儒将,該方法會(huì)根據(jù)config解析每個(gè)模塊的信息并保存到this.RemoteModules中.<code>_genModules</code>會(huì)歷遍所有的remoteModules,根據(jù)每個(gè)模塊的配置信息(如何生成配置信息下面會(huì)提到)和module索引ID來創(chuàng)建每個(gè)模塊.
react為了性能的優(yōu)化,當(dāng)js兩次調(diào)用方法的間隔小于MIN_TIME_BETWEEN_FLUSHES_MS(5ms)時(shí)間对蒲,會(huì)將調(diào)用信息先緩存到_queue中钩蚊,等待下次在一并提交給native層執(zhí)行.
Navigator組件到NavigationExperimental組件
Navigator and NavigatorIOS兩個(gè)都是有狀態(tài)(即保存各個(gè)導(dǎo)航的序順)的組件拢蛋,允許你的APP在多個(gè)不同的場(chǎng)景(屏幕)之間管理你的導(dǎo)航。這兩個(gè)導(dǎo)航管理了一個(gè)路由棧(route stack)技俐,這樣就允許我們使用pop(), push()和replace()來管理狀態(tài)昼牛。NavigatorIOS是使用了iOS的 UINavigationController類,而Navigator都是基于Javascript蝠咆。 Navigator適用于兩個(gè)平臺(tái)踊东,而NavigatorIOS只能適用于iOS. 如果在一個(gè)APP中應(yīng)用了多個(gè)導(dǎo)航組件(Navigator and NavigatorIOS一起使用). 那么在兩者之間進(jìn)行導(dǎo)航過渡,會(huì)變得非常困難.
NavigationExperimental以一種新的方法實(shí)現(xiàn)導(dǎo)航邏輯刚操,這樣允許任何的視圖都可以作為導(dǎo)航的視圖 闸翅。它包含了一個(gè)預(yù)編異的組件NavigationAnimatedView來管理場(chǎng)景間的動(dòng)畫。它內(nèi)部的每一個(gè)視圖都可以有自己的手勢(shì)和動(dòng)畫菊霜。
React Native項(xiàng)目已經(jīng)不再維護(hù)Navigator組件而全面轉(zhuǎn)向NavigationExperimental組件了坚冀。NavigationExperimental改進(jìn)了Navigator組件的一下幾個(gè)方面:
單向數(shù)據(jù)流, 它使用reducers 來操作最頂層的state 對(duì)像,而在Navigator中鉴逞,當(dāng)你在子導(dǎo)航頁中记某,不可能操作到app最初打開頁面時(shí)的state對(duì)像,除非构捡,一級(jí)級(jí)的通過props傳遞過方法名或函數(shù)名液南,然后在子頁面中調(diào)用這些方法或者函數(shù),來修改某個(gè)頂層的數(shù)據(jù)勾徽。
為了允許存在本地和基于 js的導(dǎo)航視圖贺拣,導(dǎo)航的邏輯和路由,必須從視圖邏輯中獨(dú)立出來捂蕴。
改進(jìn)了切換時(shí)的場(chǎng)景動(dòng)畫譬涡,手勢(shì)和導(dǎo)航欄
NavigationExperimental的使用:
實(shí)現(xiàn)方案可參考此處。
具體使用也可以看這里.