Rx,不管你是JS砸民,Java抵怎,Python還是Swift,玩的就是操作符岭参。每個操作符怎么用反惕,官方文檔寫得不能再清楚了,再配上例子和圖演侯,您要還整不明白就……繼續(xù)整姿染,整到明白為止,量變肯定會導(dǎo)致質(zhì)變秒际,就像國足永遠(yuǎn)不爭氣悬赏,都是永恒的真理。
然而整明白了操作符是干啥的程癌,確不知道在啥地方用舷嗡,這就是cookbook或者recipe之類書籍有市場的原因。說白了就像中國的教育嵌莉,理論都是大拿进萄,實踐都是newbie。
當(dāng)然锐峭,概念還是要講的中鼠,理論是基礎(chǔ),實踐是真理沿癞。下面我們來看一個場景:一個web app援雇,電腦上用,用鼠標(biāo)椎扬,手機(jī)平板上用惫搏,用手指具温,這不廢話么!然而懂行的人知道里面有奧妙筐赔,用鼠標(biāo)的铣猩,會觸發(fā)mousedown,mouseup茴丰,mousemove事件达皿,用手指的,會觸發(fā)touchstart贿肩,touchend峦椰,touchmove事件。拋出來的問題是汰规,能不能只寫一套邏輯供兩套事件使用呢汤功?當(dāng)然能了,拷貝粘貼唄控轿。我們來看看RxJS中怎么做冤竹。
const mouseupStream = Rx.Observable.fromEvent(document, 'mouseup');// 鼠標(biāo)按鍵抬起事件流
const touchendStream = Rx.Observable.fromEvent(document, 'touchend');// 觸摸屏幕抬起事件流
mouseupStream.subscribe(/*處理邏輯*/);
touchedStream.subscribe(/*處理邏輯*/);
這不和沒用RxJS一個意思么。別著急啊茬射,沒說這是最佳解決方案啊鹦蠕,接著往下看。
這兩個事件是非常類似的在抛,既然類似钟病,我們能不能把它們當(dāng)成一樣的事件,合并起來呢刚梭?答案是肠阱,必須可以!
RxJS中的merge()操作符就是用來合并兩個流的朴读,既然是兩個屹徘,就會有順序問題,如果是同步操作衅金,那就是有序的噪伊;如果是異步操作,merge()操作符內(nèi)部會根據(jù)時間來做決定氮唯,合并起來的流中的事件就是無序的鉴吹,交叉出現(xiàn)的。這里說句題外話惩琉,其實將操作符都要配圖的豆励,但是我實在是懶,況且圖也不是我畫的瞒渠,大家就自己上網(wǎng)站上看吧良蒸。
merge()既有靜態(tài)方法實現(xiàn)也有實例方法實現(xiàn)技扼,以后講操作符不額外聲明的話,默認(rèn)都有兩種實現(xiàn)嫩痰。
Rx.Observable.merge(mouseupStream, touchendStream);
//或
mouseupStream.merge(touchendStream);
假如我們最終的需求是要鼠標(biāo)點擊或手指觸摸位置的數(shù)據(jù)淮摔,請看代碼:
Rx.Observable.merge(mouseupStream, touchendStream)
.do(event => console.log(event.type))// debug,查看事件類型
.map(event => {
switch (event.type) {
case 'touchend':
return {
left: event.changedTouches[0].clientX,
top: event.changedTouches[0].clientY
};
case 'mouseup':
return {
left: event.clientX,
top: event.clientY
}
}
})
.subscribe(object => {
console.log(`位置坐標(biāo)為:(${object.left}, ${object.top})`);
})
我們知道Rx一脈相承自函數(shù)式編程始赎,那這段代碼就有點說不過去了,怎能出現(xiàn)命令式的控制語句呢仔燕,說的就是你switch造垛!當(dāng)然理想化的東西能不能實現(xiàn)還是一回事兒呢,況且規(guī)則是人定的晰搀。但我們盡量在操作符中不出現(xiàn)命令式語句五辽,把不得不出現(xiàn)的邏輯推遲到observer端來處理。我在系列三中提到過副作用也都放到observer來處理外恕。
當(dāng)然更好的方式是杆逗,我們在observable端就把數(shù)據(jù)處理好,observer接收時就不用再做處理了鳞疲。
const pmouseupStream = mouseupStream.map(event => ({
left: event.clientX,
top: event.clientY
}));
const ptouchendStream = touchendStream.map(event => ({
left: event.changedTouches[0].clientX,
top: event.changedTouches[0].clientY
}));
Rx.Observable.merge(pmouseupStream, ptouchendStream)
.subscribe(object => {
console.log(`位置坐標(biāo)為:(${object.left}, ${object.top})`);
});
完美罪郊!
沒有任何事會一直完美下去的。如果我們想要兩個異步流合并后保持先后順序呢尚洽?沒問題悔橄,concat()操作符完美解決你的問題。這個沒啥可講的腺毫,同步流能用嗎癣疟?您自己試試吧,實踐才是真理潮酒。
繼續(xù)繼續(xù)睛挚。剛才命令式的switch被我們痛罵了一頓,RxJS中的switch()可就是個寶兒嘍急黎。叫李嘉誠的千千萬扎狱,然而首富就那么一個,這就是差距叁熔。
switch()操作符只有實例方法實現(xiàn)方式委乌。它的作用是切換到最新的那個observable。這到底是啥意思呢荣回,請看圖:
當(dāng)我們第一次點擊按鈕的時候遭贸,map中的函數(shù)返回值又是一個observable,也就是說心软,switch接收到的是一個內(nèi)嵌observable的observable壕吹,這時候switch會用內(nèi)嵌的observable取代外層的那個observable著蛙,也就是click事件流被新產(chǎn)生的時間事件流取代了。
當(dāng)我們在第二次點擊按鈕的時候(這個時候上個時間事件流還沒有結(jié)束)耳贬,又會產(chǎn)生一個新的時間事件流踏堡,這個新的時間事件流不僅取代了click事件流,還取代了第一次點擊按鈕產(chǎn)生的那個時間事件流咒劲。謹(jǐn)記switch永遠(yuǎn)會切換到最新的那個事件流顷蟆。
內(nèi)嵌observable引出了merge(),concat()腐魂,switch()各自的兄弟:mergeMap()帐偎,concatMap(),switchMap()蛔屹。
mergeMap
還用上面的例子削樊,把map改成mergeMap,去掉switch兔毒,區(qū)別是漫贞,click事件流同樣被取代,但第一次點擊產(chǎn)生的時間事件流不會被第二次點擊的時間事件流取代育叁,而是合并成了一個流(無序)迅脐。
switchMap
這個操作符完成的事兒實際就是上面例子中的map+switch。
concatMap
用concatMap替換map豪嗽,去掉switch仪际,點擊三次按鈕,我們會看到控制臺輸出三次0到4昵骤,前一次不結(jié)束树碱,后面的一直等待。這里給大家一個贊賞我的機(jī)會变秦,請用三個鼠標(biāo)事件流+concatMap操作符+takeUntil操作符完成拖放頁面元素的功能成榜。你會發(fā)現(xiàn),哇~好簡單好明了蹦玫。
takeUntil操作符接收一個observable為參數(shù)赎婚,含義是,接收上游事件并讓它通過樱溉,直到參數(shù)observable開始發(fā)送事件挣输。
其實更直觀的感受這些操作符的強(qiáng)大之處,或者說Rx的強(qiáng)大之處福贞,應(yīng)該用ajax撩嚼、promise這些更貼近日常開發(fā)的例子,譬如說之前提到過的搜索框提示,或者監(jiān)控股票價格完丽,氣象溫度等等恋技,就留個各位自己嘗試吧,實踐出真知嘛逻族。