摘自?
折騰范兒の味精 博文。
ReactNative 概要
ReactNative览濒,動態(tài)次和,跨平臺反肋,熱更新,這幾個(gè)詞現(xiàn)在越來越火了踏施,一句使用JavaScript寫原生App吸引力了無數(shù)人的眼球石蔗,并且誕生了這么久也逐漸趨于穩(wěn)定,攜程,天貓,QZone也都在大產(chǎn)品線的業(yè)務(wù)上畅形,部分模塊采用這個(gè)方案上線养距,并且效果得到了驗(yàn)證(見2016 GMTC 資料PPT)。
我們把這個(gè)單詞拆解成2部分
React
熟悉前端的朋友們可能都知道React.JS這個(gè)前端框架日熬,沒錯(cuò)整個(gè)RN框架的JS代碼部分棍厌,就是React.JS,所有這個(gè)框架的特點(diǎn),完完全全都可以在RN里面使用(這里還融入了Flux耘纱,很好的把傳統(tǒng)的MVC重組為dispatch敬肚,store和components,Flux架構(gòu))
所以說束析,寫RN哪不懂了帘皿,去翻React.JS的文檔或許都能給你解答。
Native
顧名思義畸陡,純原生的native體驗(yàn)鹰溜,純原生的UI組件,純原生的觸摸響應(yīng)丁恭,純原生的模塊功能
那么這兩個(gè)不相干的東西是如何關(guān)聯(lián)在一起的呢曹动?
React.JS是一個(gè)前端框架,在瀏覽器內(nèi)H5開發(fā)上被廣泛使用牲览,他在渲染render()這個(gè)環(huán)節(jié)墓陈,在經(jīng)過各種flexbox布局算法之后,要在確定的位置去繪制這個(gè)界面元素的時(shí)候第献,需要通過瀏覽器去實(shí)現(xiàn)贡必。他在響應(yīng)觸摸touchEvent()這個(gè)環(huán)節(jié),依然是需要瀏覽器去捕獲用戶的觸摸行為庸毫,然后回調(diào)React.JS
上面提到的都是純網(wǎng)頁仔拟,純H5,但如果我們把render()這個(gè)事情攔截下來飒赃,不走瀏覽器利花,而是走native會怎樣呢?
當(dāng)React.JS已經(jīng)計(jì)算完每個(gè)頁面元素的位置大小载佳,本來要傳給瀏覽器炒事,讓瀏覽器進(jìn)行渲染,這時(shí)候我們不傳給瀏覽器了蔫慧,而是通過一個(gè)JS/OC的橋梁挠乳,去通過[[UIView alloc]initWithFrame:frame]的OC代碼,把這個(gè)界面元素渲染了姑躲,那我們就相當(dāng)于用React.JS繪制出了一個(gè)native的View
拿我們剛剛繪制出得native的View睡扬,當(dāng)他發(fā)生native源生的- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event觸摸事件的時(shí)候,通過一個(gè)OC/JS的橋梁肋联,去調(diào)用React.JS里面寫好的點(diǎn)擊事件JS代碼
這樣React.JS還是那個(gè)React.JS威蕉,他的使用方法沒發(fā)生變化,但是卻獲得了純源生native的體驗(yàn)橄仍,native的組件渲染,native的觸摸響應(yīng)
于是,這個(gè)東西就叫做React-Native侮繁。
ReactNative 結(jié)構(gòu)
大家可以看到虑粥,剛才我說的核心就是一個(gè)橋梁,無論是JS=>OC宪哩,還是OC=>JS娩贷。
剛才舉得例子,就相當(dāng)于把純原生的UI模塊锁孟,接入這個(gè)橋梁彬祖,從而讓原生UI與React.JS融為一體。
那我們把野心放長遠(yuǎn)點(diǎn)品抽,我們不止想讓React.JS操作UI储笑,我還想用JS操作數(shù)據(jù)庫!無論是新玩意Realm圆恤,還是老玩意CoreData突倍,F(xiàn)MDB,我都希望能用JS操作應(yīng)該怎么辦盆昙?好辦羽历,把純原生的DB代碼模塊,接入這個(gè)橋梁
如果我想讓JS操作Socket做長連接呢淡喜?好辦秕磷,把原生socket代碼模塊接入這個(gè)橋梁。如果我想讓JS能操作支付寶炼团,微信跳夭,蘋果IAP呢?好辦们镜,把原生支付代碼模塊接入這個(gè)橋梁
由此可見RN就是由一個(gè)bridge橋梁币叹,連接起了JS與na的代碼模塊
鏈接了哪個(gè)模塊,哪個(gè)模塊就能用JS來操作模狭,就能動態(tài)更新
發(fā)現(xiàn)現(xiàn)有RN框架有些功能做不到了颈抚?擴(kuò)展寫個(gè)native代碼模塊,接入這個(gè)橋梁
這是一個(gè)極度模塊化可擴(kuò)展的橋梁框架嚼鹉,不是說你從facebook的源上拉下來RN的代碼贩汉,RN的能力就固定一成不變了,他的模塊化可擴(kuò)展锚赤,讓你缺啥補(bǔ)上啥就好了匹舞。
ReactNative 結(jié)構(gòu)圖
大家可以看這個(gè)結(jié)構(gòu)圖,整個(gè)RN的結(jié)構(gòu)分為四個(gè)部分线脚,上面提到的赐稽,RN橋的模塊化可擴(kuò)展性叫榕,就體現(xiàn)在JSBridge/OCBridge里的ModuleConfig,只要遵循RN的協(xié)議RCTBridgeModule去寫的OC Module對象姊舵,使用RCT_EXPORT_MODULE()宏注冊類晰绎,使用RCT_EXPORT_METHOD()宏注冊方法,那么這個(gè)OC Module以及他的OC Method都會被JS與OC的ModuleConfig進(jìn)行統(tǒng)一控制括丁。
○ 大家可以看到RCTRootView是RN的根視圖荞下。
? ? ? ○ 他內(nèi)部持有了一個(gè)RCTBridge,但是這個(gè)RCTBridge并沒有太多的代碼,而是持有了另一個(gè) ? ? ? ? ??RCTBatchBridge對象史飞,大部分的業(yè)務(wù)邏輯都轉(zhuǎn)發(fā)給BatchBridge尖昏,BatchBridge里面寫 ? ? ? ? ? ? ? 著的大量的核心代碼
? ? ? ? ? ? ? ○ BatchBridge會通過RCTJavaScriptLoader來加載JSBundle,在加載完畢后构资,這個(gè) ? ? ? ? ? ? ? ? ? ?loader也沒什么太大的用了
? ? ? ? ? ? ? ○ BatchBridge會持有一個(gè)RCTDisplayLink抽诉,這個(gè)對象主要用于一些Timer, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?Navigator的Module需要按著屏幕渲染頻率回調(diào)JS用的蚯窥,只是給部分Module需求使 ? ? ? ? ? ? ? ? ? ?用
? ? ? ? ? ? ? ○ RCTModuleXX所有的RN的Module組件都是RCTModuleData掸鹅,無論是RN的核心系 ? ? ? ? ? ? ? ? ?統(tǒng)組件,還是擴(kuò)展的UI組件拦赠,API組件
? ? ? ? ? ? ? ? ? ? ? ? ○ RCTJSExecutor是一個(gè)很特殊的RCTModuleData巍沙,雖然他被當(dāng)做組件 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?module一起管理,統(tǒng)一注冊荷鼠,但他是系統(tǒng)組件的核心之一句携,他負(fù)責(zé)單獨(dú)開一 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?個(gè)線程,執(zhí)行JS代碼允乐,處理JS回調(diào)矮嫉,是bridge的核心通道
? ? ? ? ? ? ? ? ? ? ? ? ○ RCTEventDispatcher也是一個(gè)很特殊的RCTModuleData,雖然他被當(dāng)做組 ? ? ? ? ? ? ? ? ? ? ? ? ? ?件module一起管理牍疏,統(tǒng)一注冊蠢笋,但是他負(fù)責(zé)的是各個(gè)業(yè)務(wù)模塊通過他主動發(fā) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 起調(diào)用js,比如UIModule鳞陨,發(fā)生了點(diǎn)擊事件昨寞,是通過他主動回調(diào)JS的,他回調(diào) ? ? ? ? ? ? ? ? ? ? ? ? ? ? JS也是通過RCTJSExecutor來操作厦滤,他的作用是封裝了eventDispatcher得API ? ? ? ? ? ? ? ? ? ? ? ? ? ?來方便業(yè)務(wù)Module使用援岩。
ReactNative 初始化代碼分析
我會按著函數(shù)調(diào)用棧類似的形式梳理出一個(gè)代碼流程表,對每一個(gè)調(diào)用環(huán)節(jié)進(jìn)行簡單標(biāo)記與作用說明掏导,在整個(gè)表梳理完畢后享怀,我會一一把每個(gè)標(biāo)記進(jìn)行詳細(xì)的源碼分析和解釋
下面的代碼流程表,如果有類名+方法的趟咆,你可以直接在RN源碼中定位到具體代碼段
○RCTRootView-initWithBundleURLXXX(RootInit標(biāo)記)
? ? ○RCTBridge-initWithBundleXXX
? ? ? ? ○RCTBridge-createBatchedBridge(BatchBridgeInit標(biāo)記)
? ? ? ? ? ? ○New Displaylink(DisplaylinkInit標(biāo)記)
? ? ? ? ? ? ○New dispatchQueue (dispatchQueueInit標(biāo)記)
? ? ? ? ? ? ○New dispatchGroup (dispatchGroupInit標(biāo)記)
? ? ? ? ? ? ○group Enter(groupEnterLoadSource標(biāo)記)
? ? ? ? ? ? ? ? ? ○RCTBatchedBridge-loadSource (loadJS標(biāo)記)
? ? ? ? ? ? ○RCTBatchedBridge-initModulesWithDispatchGroup(InitModule標(biāo)記這塊內(nèi)容非 ? ? ? ? ? ? ? ? 常多添瓷,有個(gè)子代碼流程表)
? ? ? ? ? ? ○group Enter(groupEnterJSConfig標(biāo)記)
? ? ? ? ? ? ? ? ? ○RCTBatchedBridge-setUpExecutor(configJSExecutor標(biāo)記)
? ? ? ? ? ? ? ? ? ○RCTBatchedBridge-moduleConfig(moduleConfig標(biāo)記)
? ? ? ? ? ? ? ? ? ○RCTBatchedBridge-injectJSONConfiguration(moduleConfigInject標(biāo)記)
? ? ? ? ? ? ○group Notify(groupDone標(biāo)記)
? ? ? ? ? ? ? ? ? ○RCTBatchedBridge-executeSourceCode(evaluateJS標(biāo)記)
? ? ? ? ? ? ? ? ? ○RCTDisplayLink-addToRunLoop(addrunloop標(biāo)記)
RootInit標(biāo)記:所有RN都是通過init方法創(chuàng)建的不再贅述梅屉,URL可以是網(wǎng)絡(luò)url,也可以是本地filepath轉(zhuǎn)成URL
BatchBridgeInit標(biāo)記:前邊說過rootview會先持有一個(gè)RCTBridge仰坦,所有的module都是直接操作bridge所提供的接口履植,但是這個(gè)bridge基本上不干什么核心邏輯代碼计雌,他內(nèi)部持有了一個(gè)batchbridge悄晃,各種調(diào)用都是直接轉(zhuǎn)發(fā)給RCTBatchBridge來操作,因此batchbridge才是核心
RCTBridge在init的時(shí)候調(diào)用[self setUp]
RCTBridge在setUp的時(shí)候調(diào)用[self createBatchedBridge]
DisplaylinkInit標(biāo)記:batchbridge會首先初始化一個(gè)RCTDisplayLink這個(gè)東西在業(yè)務(wù)邏輯上不會被所有的module調(diào)用凿滤,他的作用是以設(shè)備屏幕渲染的頻率觸發(fā)一個(gè)timer妈橄,判斷是否有個(gè)別module需要按著timer去回調(diào)js,如果沒有module翁脆,這個(gè)模塊其實(shí)就是空跑一個(gè)displaylink眷蚓,注意,此時(shí)只是初始化反番,并沒有run這個(gè)displaylink
dispatchQueueInit標(biāo)記:會初始化一個(gè)GCDqueue沙热,后面很多操作都會被扔到這個(gè)隊(duì)列里,以保證順序執(zhí)行
dispatchGroupInit標(biāo)記:后面接下來進(jìn)行的一些列操作罢缸,都會被添加到這個(gè)GCDgroup之中篙贸,那些被我做了group Enter標(biāo)記的,當(dāng)group內(nèi)所有事情做完之后枫疆,會觸發(fā)group Notify
groupEnterLoadSource標(biāo)記:會把無論是從網(wǎng)絡(luò)還是從本地爵川,拉取jsbundle這個(gè)操作,放進(jìn)GCDgroup之中息楔,這樣只有這個(gè)操作進(jìn)行完了(還有其他group內(nèi)操作執(zhí)行完了寝贡,才會執(zhí)行notify的任務(wù))
loadJS標(biāo)記:其實(shí)就是異步去拉取jsbundle,無論是本地讀還是網(wǎng)絡(luò)啦值依,[RCTJavaScriptLoader loadBundleAtURL:self.bundleURL onComplete:onSourceLoad];只有當(dāng)回調(diào)完成之后會執(zhí)行dispatch_group_leave圃泡,離開group
InitModule標(biāo)記:這個(gè)函數(shù)是在主線程被執(zhí)行的,但是剛才生成的GCD group會被當(dāng)做參數(shù)傳進(jìn)內(nèi)部愿险,因?yàn)閮?nèi)部的一些邏輯是需要加入group的颇蜡,這個(gè)函數(shù)內(nèi)部很復(fù)雜 我會繼續(xù)繪制一個(gè)代碼流程表
1)RCTGetModuleClasses()
一個(gè)C函數(shù),RCT_EXPORT_MODULE()注冊宏會在+load時(shí)候把Module類都統(tǒng)一管理在一個(gè)static NSArray里拯啦,通過RCTGetModuleClasses()可以取出來所有的Module
2)RCTModuleData-initWithModuleClass
此處是一個(gè)for循環(huán)澡匪,循環(huán)剛才拿到的array,對每一個(gè)注冊了得module都循環(huán)生成RCTModuleData實(shí)例
3)配置moduleConfig
每一個(gè)module在循環(huán)生成結(jié)束后褒链,bridge會統(tǒng)一存儲3分配置表唁情,包含了所有的moduleConfig的信息,便于查找和管理.