說是iOS轉(zhuǎn)React Native,不如說是iOS轉(zhuǎn)JavaScript最需要轉(zhuǎn)變的思路。
本質(zhì)上來說JavaScript是單線程語言;同時(shí),可能因?yàn)樗菫榫W(wǎng)絡(luò)而生,需要高頻度地向服務(wù)器讀取數(shù)據(jù);再者,可能因?yàn)樗诘目蛻舳恕獮g覽器——的執(zhí)行效率并不高;因此,它采取了大量的異步化操作。例如,要裝載圖片,需要有一個(gè)異步讀取圖片的過程;要讀取文件/數(shù)據(jù)庫,也必須異步讀??;可能最令iOS程序員發(fā)指的是,如果要使用原生的iOS模塊,那么必然會(huì)要用異取讀取。
所以,原來簡單的iOS程序,就會(huì)變得支離破碎。就拿讀文件來說,在iOS程序中,你只需要在一個(gè)模塊中按順序?qū)懴聛砭秃茫?/p>
1)獲得文件路徑
2)打開文件,讀取文件內(nèi)容(若讀取時(shí)間長,頂多來一個(gè)UI轉(zhuǎn)圈界面展示)
3)使用文件內(nèi)容
轉(zhuǎn)到RN中呢,你需要
1)獲得文件路徑
2)開一個(gè)子線程去讀文件
3)準(zhǔn)備一個(gè)回調(diào)(callback)函數(shù),等待子線程讀取后再返回
看起來好像沒有什么區(qū)別,但是,由于JS是單線程函數(shù),你還不能在主線程中單純等待返回。真實(shí)的情況是,主線程執(zhí)行完了,才會(huì)去執(zhí)行子線程。因此,一個(gè)簡單的讀取文件的過程,就不得不寫成好像從服務(wù)器異步讀取數(shù)據(jù)一樣麻煩。
單是讀取文件也罷了,若是你寫了一個(gè)iOS原生模塊,是各種工具函數(shù)的集合,然后會(huì)在RN模塊中反復(fù)調(diào)用。那么,你就需要在等待一個(gè)函數(shù)返回之后,再調(diào)用另一個(gè)函數(shù),于是,在多次調(diào)用之后,你發(fā)現(xiàn)已陷入到傳說中的“回調(diào)地獄”!
說實(shí)話,這是我從iOS編程轉(zhuǎn)向RN的過程中,最不習(xí)慣的一道坎!比起編程環(huán)境搭建,比起各種模塊的調(diào)用方式,這個(gè)思路上的轉(zhuǎn)變最讓人鬧心。
最后,還是找到一些相對簡單的解決方法。以下這篇文章給出了最有用的一種方法:
http://blog.csdn.net/kunshan_shenbin/article/details/40425143
簡單說,就是用特定的語法方式,讓編寫代碼的過程,變得不再大括號套小括號。以下是這種方法使用的簡單流程。
1.在文件頭引入Generator、以及next控制函數(shù)的工具性代碼
// 當(dāng)前的 Generator
let activeGenerator;
// 處理 g.next() 功能
function gNext() {
return function(err, data) {
if(err) {
throw err;
}
// g.next(),并把回調(diào)函數(shù)的結(jié)果作為參數(shù)傳遞給 yield
activeGenerator.next(data)
}
}
// 控制工具
function gQueue(generatorFunc) {
activeGenerator= generatorFunc(gNext());
activeGenerator.next();
}
2.異步調(diào)用的時(shí)候使用這種語法開頭
// 該語句實(shí)際產(chǎn)生了一個(gè)generator函數(shù),并定義了其中的next操作
gQueue(function * flow(next) {
“flow”這個(gè)名字是隨便取的,這里表示它是一個(gè)流程控制工具,該名字不會(huì)被再次調(diào)用。接下來,我們在gQueue中,就能大膽地像寫同步語法一樣,使用異步調(diào)用了:
let result1 = yield (callBack => {
someModule.someFunc1(var1, var2..., callBack);
})(next);
// 以下語句會(huì)在result1獲得值后繼續(xù)執(zhí)行
let result2 = yield (callBack => {
someModule.someFunc2(result, var2..., callBack);
})(next);
實(shí)際上,由于generator函數(shù)的特性,每次.next()調(diào)用,都會(huì)來到新的一條yield語句。然后,該函數(shù)所在線程會(huì)暫停,開始調(diào)用yield語句中的匿名函數(shù)。在匿名函數(shù)中,我們放置了自己所想要調(diào)用的異步操作:someModule.someFunc。
之后的方式非常巧妙,注意,上述匿名函數(shù)所使用的形參callBack,在調(diào)用的時(shí)候,被實(shí)參 next 替代。因此,callBack返回,實(shí)際上是調(diào)用了 next 函數(shù)。而根據(jù)最前面的工具定義,next 對應(yīng)的是 gNext() 函數(shù)【JS的語法這個(gè)地方有點(diǎn)繞,需要多看幾遍】,因此,callBack函數(shù)中,定義的第一個(gè)參數(shù) error 用來描述返回結(jié)果是否有誤,而第二個(gè)參數(shù)data,則被當(dāng)成返回值傳給了yield。那么,我們的 result 就獲得了這個(gè)data值。
更巧妙的是,當(dāng) g.next(data)除了返回data值,實(shí)質(zhì)上還執(zhí)行了一次.next操作。那么,generator函數(shù)所有線程將繼續(xù)執(zhí)行,這樣就保證了后續(xù)語句立刻會(huì)在返回后運(yùn)行。實(shí)現(xiàn)了看似“同步連續(xù)執(zhí)行”的效果。
如果對以上機(jī)制仍感模糊,阮一峰老師的這篇文章值得推薦:Generator 函數(shù)的語法。
3.回調(diào)函數(shù)callBack的特殊規(guī)則
根據(jù)以上機(jī)制,要正確地使用該方法,必須對我們使用的callBack使用特定的返回機(jī)制。callBack的第一個(gè)參數(shù) error 必須用來描述返回結(jié)果是否有誤,而第二個(gè)參數(shù)data則用于返回真正需要的數(shù)據(jù)。假如你返回的數(shù)據(jù)有多個(gè),那么必須打包成一個(gè)數(shù)據(jù),然后在返回值里再做反向解析。以下是這一callBack機(jī)制的正確使用方式:
function someFunction(var1, var2, callback) {
// some operation,最后得到兩個(gè)值要返回
let result1 = ...;
let result2 = ...;
callback(null, [result1, result2]);
}
最后的語句中,偷懶把錯(cuò)誤值定成了null,待返回的兩個(gè)值,包裝成一個(gè)數(shù)組作為data返回過去了。那么后面調(diào)用時(shí),獲得這個(gè)兩個(gè)值,就必須分別從結(jié)果中以數(shù)組方式解析。
4.其它
這篇文章只是描述了異步調(diào)用的一個(gè)最大障礙。相關(guān)的問題還有 setTimeout 函數(shù)的應(yīng)用,以及針對異步模塊調(diào)用的監(jiān)聽等技術(shù)方案,將來有空再來細(xì)述。