說是iOS轉React Native蹂随,不如說是iOS轉JavaScript最需要轉變的思路。
本質上來說JavaScript是單線程語言玉罐;同時屈嗤,可能因為它是為網絡而生,需要高頻度地向服務器讀取數(shù)據(jù)吊输;再者饶号,可能因為它所在的客戶端——瀏覽器——的執(zhí)行效率并不高;因此季蚂,它采取了大量的異步化操作茫船。例如,要裝載圖片扭屁,需要有一個異步讀取圖片的過程算谈;要讀取文件/數(shù)據(jù)庫,也必須異步讀确杞痢濒生;可能最令iOS程序員發(fā)指的是,如果要使用原生的iOS模塊幔欧,那么必然會要用異取讀取罪治。
所以丽声,原來簡單的iOS程序,就會變得支離破碎觉义。就拿讀文件來說雁社,在iOS程序中,你只需要在一個模塊中按順序寫下來就好:
1)獲得文件路徑
2)打開文件晒骇,讀取文件內容(若讀取時間長霉撵,頂多來一個UI轉圈界面展示)
3)使用文件內容
轉到RN中呢,你需要
1)獲得文件路徑
2)開一個子線程去讀文件
3)準備一個回調(callback)函數(shù)洪囤,等待子線程讀取后再返回
看起來好像沒有什么區(qū)別徒坡,但是,由于JS是單線程函數(shù)瘤缩,你還不能在主線程中單純等待返回喇完。真實的情況是,主線程執(zhí)行完了剥啤,才會去執(zhí)行子線程锦溪。因此,一個簡單的讀取文件的過程府怯,就不得不寫成好像從服務器異步讀取數(shù)據(jù)一樣麻煩刻诊。
單是讀取文件也罷了,若是你寫了一個iOS原生模塊牺丙,是各種工具函數(shù)的集合则涯,然后會在RN模塊中反復調用。那么赘被,你就需要在等待一個函數(shù)返回之后是整,再調用另一個函數(shù),于是民假,在多次調用之后浮入,你發(fā)現(xiàn)已陷入到傳說中的“回調地獄”!
說實話羊异,這是我從iOS編程轉向RN的過程中事秀,最不習慣的一道坎!比起編程環(huán)境搭建野舶,比起各種模塊的調用方式易迹,這個思路上的轉變最讓人鬧心。
最后平道,還是找到一些相對簡單的解決方法睹欲。以下這篇文章給出了最有用的一種方法:
http://blog.csdn.net/kunshan_shenbin/article/details/40425143
簡單說,就是用特定的語法方式,讓編寫代碼的過程窘疮,變得不再大括號套小括號袋哼。以下是這種方法使用的簡單流程。
1.在文件頭引入Generator闸衫、以及next控制函數(shù)的工具性代碼
// 當前的 Generator
let activeGenerator;
// 處理 g.next() 功能
function gNext() {
return function(err, data) {
if(err) {
throw err;
}
// g.next()涛贯,并把回調函數(shù)的結果作為參數(shù)傳遞給 yield
activeGenerator.next(data)
}
}
// 控制工具
function gQueue(generatorFunc) {
activeGenerator= generatorFunc(gNext());
activeGenerator.next();
}
2.異步調用的時候使用這種語法開頭
// 該語句實際產生了一個generator函數(shù),并定義了其中的next操作
gQueue(function * flow(next) {
“flow”這個名字是隨便取的蔚出,這里表示它是一個流程控制工具弟翘,該名字不會被再次調用。接下來骄酗,我們在gQueue中稀余,就能大膽地像寫同步語法一樣,使用異步調用了:
let result1 = yield (callBack => {
someModule.someFunc1(var1, var2..., callBack);
})(next);
// 以下語句會在result1獲得值后繼續(xù)執(zhí)行
let result2 = yield (callBack => {
someModule.someFunc2(result, var2..., callBack);
})(next);
實際上酥筝,由于generator函數(shù)的特性滚躯,每次.next()調用,都會來到新的一條yield語句嘿歌。然后,該函數(shù)所在線程會暫停茁影,開始調用yield語句中的匿名函數(shù)宙帝。在匿名函數(shù)中,我們放置了自己所想要調用的異步操作:someModule.someFunc募闲。
之后的方式非常巧妙步脓,注意,上述匿名函數(shù)所使用的形參callBack浩螺,在調用的時候靴患,被實參 next 替代。因此要出,callBack返回鸳君,實際上是調用了 next 函數(shù)。而根據(jù)最前面的工具定義患蹂,next 對應的是 gNext() 函數(shù)【JS的語法這個地方有點繞或颊,需要多看幾遍】,因此传于,callBack函數(shù)中囱挑,定義的第一個參數(shù) error 用來描述返回結果是否有誤,而第二個參數(shù)data沼溜,則被當成返回值傳給了yield平挑。那么,我們的 result 就獲得了這個data值系草。
更巧妙的是通熄,當 g.next(data)除了返回data值否淤,實質上還執(zhí)行了一次.next操作。那么棠隐,generator函數(shù)所有線程將繼續(xù)執(zhí)行石抡,這樣就保證了后續(xù)語句立刻會在返回后運行。實現(xiàn)了看似“同步連續(xù)執(zhí)行”的效果助泽。
如果對以上機制仍感模糊啰扛,阮一峰老師的這篇文章值得推薦:Generator 函數(shù)的語法。
3.回調函數(shù)callBack的特殊規(guī)則
根據(jù)以上機制嗡贺,要正確地使用該方法隐解,必須對我們使用的callBack使用特定的返回機制。callBack的第一個參數(shù) error 必須用來描述返回結果是否有誤诫睬,而第二個參數(shù)data則用于返回真正需要的數(shù)據(jù)煞茫。假如你返回的數(shù)據(jù)有多個,那么必須打包成一個數(shù)據(jù)摄凡,然后在返回值里再做反向解析续徽。以下是這一callBack機制的正確使用方式:
function someFunction(var1, var2, callback) {
// some operation,最后得到兩個值要返回
let result1 = ...;
let result2 = ...;
callback(null, [result1, result2]);
}
最后的語句中亲澡,偷懶把錯誤值定成了null钦扭,待返回的兩個值,包裝成一個數(shù)組作為data返回過去了床绪。那么后面調用時客情,獲得這個兩個值,就必須分別從結果中以數(shù)組方式解析癞己。
4.其它
這篇文章只是描述了異步調用的一個最大障礙膀斋。相關的問題還有 setTimeout 函數(shù)的應用,以及針對異步模塊調用的監(jiān)聽等技術方案痹雅,將來有空再來細述仰担。