亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

RxJS 教程

KnewOne / 2979人閱讀

摘要:是針對(duì)異步數(shù)據(jù)流的編程。所以這個(gè)數(shù)據(jù)流只包含一個(gè)簡(jiǎn)單的反射值。且慢,天生就是處理異步數(shù)據(jù)流的,為何不把請(qǐng)求的響應(yīng)作為一個(gè)攜帶數(shù)據(jù)的流呢么么噠,概念上沒(méi)有問(wèn)題,我們就來(lái)操作一下。你需要明確通知觀察者或者訂閱者數(shù)據(jù)流的到達(dá)或者錯(cuò)誤的發(fā)生。

"Reactive Programming是神馬?"

互聯(lián)網(wǎng)上有很多不是很友好的解釋。維基百科 寬泛而玄乎。 Stackoverflow教科書(shū)式的解釋非常不適合信任Reactive Manifesto 聽(tīng)起來(lái)像是給給項(xiàng)目經(jīng)理或者是銷售的匯報(bào)。 微軟的 Rx 定義 "Rx = Observables + LINQ + Schedulers" 太重并且太微軟化了,讓人看起來(lái)不知所云?!绊憫?yīng)”、“變化發(fā)生”這些術(shù)語(yǔ)無(wú)法很好地闡釋Reactive Programming的顯著特點(diǎn),聽(tīng)起來(lái)和你熟悉的MV*、編程語(yǔ)言差別不大。 當(dāng)然,我的視角也是基于模型和變換的,要是脫離了這些概念,一切都是無(wú)稽之談了。

那么我要開(kāi)始吧啦吧啦了,(后文中,將使用RP代替Reactive Programming,私底下譯者將Reactive Programming,翻譯為響應(yīng)式編程)。

RP 是針對(duì)異步數(shù)據(jù)流的編程。

一定程度而言,RP并不算新的概念。Event Bus、點(diǎn)擊事件都是異步流。開(kāi)發(fā)者可以觀測(cè)這些異步流,并調(diào)用特定的邏輯對(duì)它們進(jìn)行處理。使用Reactive如同開(kāi)掛:你可以創(chuàng)建點(diǎn)擊、懸停之類的任意流。通常流廉價(jià)(點(diǎn)擊一下就出來(lái)一個(gè))而無(wú)處不在,種類豐富多樣:變量,用戶輸入,屬性,緩存,數(shù)據(jù)結(jié)構(gòu)等等都可以產(chǎn)生流。舉例來(lái)說(shuō):微博回文(譯者注:比如你關(guān)注的微博更新了)和點(diǎn)擊事件都是流:你可以監(jiān)聽(tīng)流并調(diào)用特定的邏輯對(duì)它們進(jìn)行處理。

基于流的概念,RP賦予了你一系列神奇的函數(shù)工具集,使用他們可以合并、創(chuàng)建、過(guò)濾這些流。 一個(gè)流或者一系列流可以作為另一個(gè)流的輸入。你可以 合并 兩個(gè)流,從一堆流中 過(guò)濾 你真正感興趣的那一些,將值從一個(gè)流 映射 到另一個(gè)流。

如果流是RP的核心,我們不妨從“點(diǎn)擊頁(yè)面中的按鈕”這個(gè)熟悉的場(chǎng)景詳細(xì)地了解它。

流是包含了有時(shí)序,正在進(jìn)行事件的序列,可以發(fā)射(emmit)值(某種類型)、錯(cuò)誤、完成信號(hào)。流在包含按鈕的瀏覽器窗口被關(guān)閉時(shí)發(fā)出完成信號(hào)。

我們異步地捕獲發(fā)射的事件,定義一系列函數(shù)在值被發(fā)射后,在錯(cuò)誤被發(fā)射后,在完成信號(hào)被發(fā)射后執(zhí)行。有時(shí),我們忽略對(duì)錯(cuò)誤,完成信號(hào)地處理,僅僅關(guān)注對(duì)值的處理。對(duì)流進(jìn)行監(jiān)聽(tīng),通常稱為訂閱,處理流的函數(shù)是觀測(cè)者,流是被觀測(cè)的主體。這就是觀測(cè)者設(shè)計(jì)模式。

教程中,我們有時(shí)會(huì)使用ASCII字符來(lái)繪制圖表:

--a---b-c---d---X---|->

a, b, c, d 是數(shù)據(jù)流發(fā)射的值
X 是數(shù)據(jù)流發(fā)射的錯(cuò)誤
| 是完成信號(hào)
---> 是時(shí)序軸

嗶嗶完了,我們來(lái)點(diǎn)新的,不然很快你就感覺(jué)到寂寞了。我們將把原來(lái)的點(diǎn)擊事件流轉(zhuǎn)換為新的點(diǎn)擊事件流。

首先我們創(chuàng)建一個(gè)計(jì)數(shù)流來(lái)表明按鈕被點(diǎn)擊的次數(shù)。在RP中,每一個(gè)流都擁有一系列方法,例如map,filter,scan 等等。當(dāng)你在流上調(diào)用這些方法,例如clickStream.map(f),會(huì)返回基于點(diǎn)擊事件流的新的流,同時(shí)原來(lái)的點(diǎn)擊事件流并不會(huì)被改變,這個(gè)特性被稱為不可變性(immutability)。不可變性與RP配合相得益彰,如同美酒加咖啡。我們可以鏈?zhǔn)降卣{(diào)用他們:clickStream.map(f).scan(g)

  clickStream: ---c----c--c----c------c--->
               vvvvv map(c becomes 1) vvvvv
               ---1----1--1----1------1--->
               vvvvvvvvv  scan(+) vvvvvvvvv
counterStream: ---1----2--3----4------5--->

map(f) 函數(shù)對(duì)原來(lái)的流使用我們出入的f函數(shù)進(jìn)行轉(zhuǎn)換,并生成新的流。在上面的例子中,我們將每一次點(diǎn)擊映射為數(shù)字1。scan(g)函數(shù)將所有流產(chǎn)生的值進(jìn)行匯總,通過(guò)傳入x = g(accumulated, current)函數(shù)產(chǎn)生新的值,g 是簡(jiǎn)單的求和函數(shù)。最后 counterStream在點(diǎn)擊發(fā)生后發(fā)射點(diǎn)擊事件發(fā)生的總數(shù)。

為了展示Reactive的真正力量,我們舉個(gè)例子:你想要“兩次點(diǎn)擊”事件的流,或者是“三次點(diǎn)擊”,或者是n次點(diǎn)擊的流。深呼吸一下,試著想想怎么用傳統(tǒng)的命令、狀態(tài)式方法來(lái)解決。我打賭這個(gè)這會(huì)相當(dāng)操蛋,你會(huì)搞些變量來(lái)記錄狀態(tài),還要搞些處理時(shí)延的機(jī)制。

如果用RP來(lái)解決,太他媽簡(jiǎn)單了。實(shí)際上4行代碼就可以搞定。先不要看代碼,不管你是菜鳥(niǎo)還是牛逼,使用圖表來(lái)思考可以使你更好地理解構(gòu)建這些流的方法。

灰色框里面的函數(shù)會(huì)把一個(gè)流轉(zhuǎn)換成另外一個(gè)流。首先我們把點(diǎn)擊打包到list中,如果點(diǎn)擊后消停了250毫秒,我們就重新打包一個(gè)新的list(顯然buffer(stream.throttle(250ms))就是用來(lái)干這個(gè)的,不明白細(xì)節(jié)沒(méi)有關(guān)系,反正是demo嘛)。我們?cè)诹斜砩险{(diào)用map(),將列表的長(zhǎng)度映射為一個(gè)整數(shù)的流。最后,我們通過(guò)filter(x >= 2)過(guò)濾掉整數(shù)1。哈哈:3個(gè)操作就生成了我們需要的流,現(xiàn)在我們可以訂閱(監(jiān)聽(tīng))這個(gè)流,然后來(lái)完成我們需要的邏輯了。

通過(guò)這個(gè)例子,我希望你能感受到使用RP的牛逼之處了。這僅僅是冰山一角。你可以在不同地流上(比如API響應(yīng)的流)進(jìn)行同樣的操作。同時(shí),Reactive還提供了許多其他實(shí)用的函數(shù)。

"我要在今后采用RP范式進(jìn)行編程嗎?"

RP 提高了編碼的抽象程度,你可以更好地關(guān)注在商業(yè)邏輯中各種事件的聯(lián)系避免大量細(xì)節(jié)而瑣碎的實(shí)現(xiàn),使得編碼更加簡(jiǎn)潔。

使用RP,將使得數(shù)據(jù)、交互錯(cuò)綜復(fù)雜的web、移動(dòng)app開(kāi)發(fā)收益更多。10年以前,與網(wǎng)頁(yè)的交互僅僅是提交表單、然后根據(jù)服務(wù)器簡(jiǎn)單地渲染返回結(jié)果這些事情。App進(jìn)化得越來(lái)越有實(shí)時(shí)性:修改表單中一個(gè)域可以同步地更新到后端服務(wù)器。“點(diǎn)贊”信息實(shí)時(shí)地在不同用戶設(shè)備上同步。

現(xiàn)代App中大量的實(shí)時(shí)事件創(chuàng)造了更好的交互和用戶體驗(yàn),披荊斬棘需要利劍在手,RP就是你手中的利劍。

通過(guò)實(shí)例RP編程思想

我們將從實(shí)例可以深入RP的編程思想,文章末尾,一個(gè)完整地實(shí)例應(yīng)用會(huì)被構(gòu)建,你也會(huì)理解整個(gè)過(guò)程。

我選擇 JavaScriptRxJS 作為實(shí)例的構(gòu)建工具。因?yàn)榇蠖嚅_(kāi)發(fā)者都熟悉JavaScript語(yǔ)言。Rx* library family 在各種語(yǔ)言和平臺(tái)都是實(shí)現(xiàn) (.NET, Java, Scala, Clojure, JavaScript, Ruby, Python, C++, Objective-C/Cocoa, Groovy, 等等)。無(wú)論你選擇在哪個(gè)平臺(tái)或者那種語(yǔ)言實(shí)踐RP,你都將從本教程中受益。(譯者注:Rx,即ReactiveX,其中X代表不同的語(yǔ)言和技術(shù)棧,比如.NET,Java,Scala,Ruby,Javascript。RxJS表示RP基于Javascript語(yǔ)言的實(shí)現(xiàn)。后文中Rx代表所有實(shí)現(xiàn)了RP的特定技術(shù)棧)

微博(Twitter)簡(jiǎn)易版“你可能感興趣的人”

微博主頁(yè),有一個(gè)組件會(huì)推薦給你那些你可能感興趣的人。

我們的Demo將使用這個(gè)場(chǎng)景,關(guān)注下面這些主要特性:

頁(yè)面打開(kāi)后,通過(guò)API加載數(shù)據(jù)展示3個(gè)你可能感興趣的用戶賬號(hào)

點(diǎn)擊“刷新”按鈕,重新加載三個(gè)新的用戶賬號(hào)

在一個(gè)用戶賬號(hào)上點(diǎn)擊"x" 按鈕,清除當(dāng)前這個(gè)賬戶,重新加載一個(gè)新的賬戶

每行展示賬戶的信息和這個(gè)賬戶主頁(yè)的鏈接

其他特性和按鈕我們暫且忽略,由于Twitter在最近關(guān)閉了公共API授權(quán)接口,我們選擇Github作為代替,展示GitHub用戶的賬戶。實(shí)例中我們使用該接口獲取GitHub用戶.

如果你希望先睹為快,完成后的代碼已經(jīng)發(fā)布在了Jsfiddle。

"你可能感興趣的用戶"請(qǐng)求&響應(yīng)

這個(gè)問(wèn)題使用Rx怎么解?,呵呵,我們從Rx的箴言開(kāi)始: 神馬都是流 。首先我們做最簡(jiǎn)單的部分——頁(yè)面打開(kāi)后通過(guò)API加載3個(gè)賬戶的信息。分三步走:(1)發(fā)一個(gè)請(qǐng)求(2)獲得響應(yīng)(3)依據(jù)響應(yīng)渲染頁(yè)面。那么,我們先使用流來(lái)表示請(qǐng)求。我靠,表示個(gè)請(qǐng)求用得著嗎?不過(guò)千里之行始于足下。

頁(yè)面加載時(shí),僅需要一個(gè)請(qǐng)求。所以這個(gè)數(shù)據(jù)流只包含一個(gè)簡(jiǎn)單的反射值。稍后,我們?cè)傺芯咳绾味鄠€(gè)請(qǐng)求出現(xiàn)的情況,現(xiàn)在先從一個(gè)請(qǐng)求開(kāi)始。

--a------|->

a是字符串 "https://api.github.com/users"

這個(gè)流中包含了我們希望請(qǐng)求的URL地址。一旦這個(gè)請(qǐng)求事件發(fā)生,我們可以獲知兩件事情:請(qǐng)求流發(fā)射值(字符串URL)的時(shí)間就是請(qǐng)求需要被執(zhí)行的時(shí)間,請(qǐng)求需要請(qǐng)求的地址就是請(qǐng)求流發(fā)射的值。

在Rx*中構(gòu)建一個(gè)單值的流很容易。官方術(shù)語(yǔ)中把流稱為“觀察的對(duì)象”("Observable"),因?yàn)榱骺梢员挥^察、訂閱,這么稱呼顯得很蠢,我自己把他們稱為 stream 。

var requestStream = Rx.Observable.just("https://api.github.com/users");

目前這個(gè)攜帶字符串的流沒(méi)有其他操作,我們需要在這個(gè)流發(fā)射值之后,做點(diǎn)什么:通過(guò)訂閱 這個(gè)流來(lái)實(shí)現(xiàn)。

requestStream.subscribe(function(requestUrl) {
  // 執(zhí)行異步請(qǐng)求
  jQuery.getJSON(requestUrl, function(responseData) {
    // ...
  });
}

我們采用了jQuery的Ajax回調(diào) (假設(shè)讀著已經(jīng)了解jQuery ajax回調(diào)) 來(lái)處理異步請(qǐng)求操作。 且慢,Rx天生就是處理異步 數(shù)據(jù)流的,
為何不把請(qǐng)求的響應(yīng)作為一個(gè)攜帶數(shù)據(jù)的流呢? 么么噠,概念上沒(méi)有問(wèn)題,我們就來(lái)操作一下。

requestStream.subscribe(function(requestUrl) {
  // 執(zhí)行異步請(qǐng)求
  var responseStream = Rx.Observable.create(function (observer) {
    jQuery.getJSON(requestUrl)
    .done(function(response) { observer.onNext(response); })
    .fail(function(jqXHR, status, error) { observer.onError(error); })
    .always(function() { observer.onCompleted(); });
  });
  
  responseStream.subscribe(function(response) {
    // 業(yè)務(wù)邏輯
  });
}

使用Rx.Observable.create()方法可以自定義你需要的流。你需要明確通知觀察者(或者訂閱者)數(shù)據(jù)流的到達(dá)(onNext()) 或者錯(cuò)誤的發(fā)生(onError())。這個(gè)實(shí)現(xiàn)中,我們封裝了jQuery 的異步 Promise。那么Promise也是可觀察對(duì)象嗎?

冰狗,你猜對(duì)啦!

可觀察對(duì)象(Observable)是超級(jí)Promise(原文Promise++,可以對(duì)比C,C++,C++在兼容C的同時(shí)引入了面向?qū)ο蟮忍匦?。 在Rx環(huán)境中,你可以簡(jiǎn)單的通過(guò)var stream = Rx.Observable.fromPromise(promise)將Promise轉(zhuǎn)換為可觀察對(duì)象, 我們后面將這樣使用, 唯一的區(qū)別是,可觀察對(duì)象與Promises/A+ 并不兼容, 但是理論上不會(huì)產(chǎn)生沖突。 Promise 可以看做只能發(fā)射單值的可觀察對(duì)象,Rx流則允許返回多個(gè)值。

不過(guò),可觀察對(duì)象至少和Promise一樣強(qiáng)大。如果你相信針對(duì)Promise的那些吹捧,不妨也留意一下Rx環(huán)境中的可觀察對(duì)象。

回到我們的例子,細(xì)心的你肯定看到了subscribe()的嵌套使用,這和回調(diào)函數(shù)嵌套一樣令人惱火。responseStream 的確和 requestStream 存在依賴關(guān)系。前面我們不是提到過(guò)Rx有一些牛逼的工具集嗎?在Rx中我們擁有簡(jiǎn)單的機(jī)制把一個(gè)流轉(zhuǎn)化為一個(gè)新的流,我們不妨試試。

我們先介紹 map(f)函數(shù)。該函數(shù)在流A的每個(gè)之上調(diào)用函數(shù)f() , 然后在流B上生成對(duì)應(yīng)的新值。如果在請(qǐng)求、響應(yīng)流上調(diào)用map(f),我們可以將請(qǐng)求的URL隱射為響應(yīng)流中的Promise(此時(shí)響應(yīng)流中包含了Promise的序列)。

var responseMetastream = requestStream
  .map(function(requestUrl) {
    return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
  });

我們把上面代碼執(zhí)行后的返回結(jié)果稱為 metastream (譯者注:按字面可以翻譯為“元流”,即包含流的流。類似概念例如:元編程——用于生成程序的編程方法;元知識(shí)——獲取知識(shí)的知識(shí)):包含其他流的流。沒(méi)什么嚇人的, 一個(gè)metastream會(huì)在執(zhí)行后發(fā)射一個(gè)流。 你可以把它看做一個(gè)指針 指針): 每一個(gè)發(fā)射的值是指向另外一個(gè)流的 指針 。在我們的例子中,每一個(gè)URL被映射為一個(gè)指向Promise流的指針,每一個(gè)Promise流中包含了相應(yīng)的響應(yīng)信息。

(譯者注:以下給出 metastream 的方法的解析方法,方便與下面的方法進(jìn)行對(duì)比):

responseMetastream.subscribe(function(streamedPromise) {
    // 首先展開(kāi)metastream,獲取內(nèi)部的流
    streamedPromise.subscribe(function(responseJsonObject) {
        // 返回內(nèi)部流發(fā)射的值
        return responseJsonObject;
    });
});

當(dāng)前版本響應(yīng)產(chǎn)生的metastream看起來(lái)有些讓人疑惑,似乎用處不大。當(dāng)前場(chǎng)景中,我們僅僅需要獲得簡(jiǎn)單的響應(yīng)流,流中發(fā)射的值為簡(jiǎn)單的JSON對(duì)象。使用flatMap:這個(gè)函數(shù)可以將枝干的流的值發(fā)射到主干流之上。當(dāng)然metastream的產(chǎn)生并不是bug,只是這個(gè)場(chǎng)景不適合而已,map()flatMap()都是Rx處理異步請(qǐng)求工具中的一部分。(譯者注:如果流A中包含了若干其他流,在流A上調(diào)用flatMap()函數(shù),將會(huì)發(fā)射其他流的值,并將發(fā)射的所有值組合生成新的流。)

var responseStream = requestStream
  .flatMap(function(requestUrl) {
    return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
  });

贊!響應(yīng)流是依照請(qǐng)求流定義的,如果 場(chǎng)景中生成了更多的請(qǐng)求流,我們也會(huì)生成同樣多的響應(yīng)流:

請(qǐng)求流:  --a-----b--c------------|->
響應(yīng)流:  -----A--------B-----C---|->

(小寫(xiě)字母表示請(qǐng)求, 大寫(xiě)字母代表響應(yīng))

獲得響應(yīng)流之后,我們就可以再訂閱后渲染頁(yè)面了:

responseStream.subscribe(function(response) {
  // 在瀏覽器中渲染響應(yīng)數(shù)據(jù)的邏輯
});

馬克一下目前的代碼:

var requestStream = Rx.Observable.just("https://api.github.com/users");

var responseStream = requestStream
  .flatMap(function(requestUrl) {
    return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
  });

responseStream.subscribe(function(response) {
  // 在瀏覽器中渲染響應(yīng)數(shù)據(jù)的邏輯
});
刷新“你可能感興趣的用戶”

忘了說(shuō)了,我們每一次請(qǐng)求都會(huì)返回100個(gè)GitHub用戶的數(shù)據(jù)。GitHub的API只允許我們?cè)O(shè)置頁(yè)面的偏移量但是不能設(shè)置每次獲得數(shù)據(jù)的數(shù)量。嗯,我們需要3個(gè)推薦用戶的數(shù)據(jù),其他97個(gè)就這樣浪費(fèi)了。暫時(shí)忽略這個(gè)問(wèn)題,后面我們看看怎么緩存數(shù)據(jù)來(lái)減少數(shù)據(jù)的浪費(fèi)。

每一次點(diǎn)擊刷新按鈕(高能注意:是一個(gè)按鈕,點(diǎn)擊后刷新“我可能感興趣的人”的數(shù)據(jù),而不是瀏覽器的刷新按鈕),請(qǐng)求流都會(huì)發(fā)射新的URL值,我們以此獲得新的響應(yīng)。刷新分為兩步:產(chǎn)生一個(gè)刷新按鈕被點(diǎn)擊的事件流(RP箴言:神馬都是流);訂閱刷新事件流后改變請(qǐng)求流的URL地址。RxJS提供了工具方便我們將時(shí)間監(jiān)聽(tīng)器轉(zhuǎn)換為可觀察對(duì)象。

var refreshButton = document.querySelector(".refresh");
var refreshClickStream = Rx.Observable.fromEvent(refreshButton, "click");

因?yàn)辄c(diǎn)擊刷新事件并不會(huì)攜帶需要請(qǐng)求的API的URL,我們需要把每一次點(diǎn)擊映射到真正的URL之上。具體實(shí)現(xiàn)方式是,在刷新點(diǎn)擊流發(fā)生后,我們通過(guò)產(chǎn)生隨機(jī)的頁(yè)面拼湊出URL,并向GitHub發(fā)起請(qǐng)求。

var requestStream = refreshClickStream
  .map(function() {
    var randomOffset = Math.floor(Math.random()*500);
    return "https://api.github.com/users?since=" + randomOffset;
  });

由于是簡(jiǎn)單的教程,我并沒(méi)有寫(xiě)相關(guān)的測(cè)試,但是我仍然知道原先的功能被我搞砸啦。呃。。。頁(yè)面打開(kāi)后居然沒(méi)有請(qǐng)求流了,除非我點(diǎn)擊刷新按鈕,否則數(shù)據(jù)怎么都出不來(lái)。擦。。。我希望 不管 是點(diǎn)擊刷新按鈕"_還是_"第一次打開(kāi)頁(yè)面,都可以產(chǎn)生獲得“我可能感興趣的人”的數(shù)據(jù)的GitHub的請(qǐng)求流。

把兩個(gè)流分開(kāi)寫(xiě)特別簡(jiǎn)單,我們已經(jīng)知道怎么做了:

var requestOnRefreshStream = refreshClickStream
  .map(function() {
    var randomOffset = Math.floor(Math.random()*500);
    return "https://api.github.com/users?since=" + randomOffset;
  });
  
var startupRequestStream = Rx.Observable.just("https://api.github.com/users");

但是我們?cè)趺窗褍蓚€(gè)流“合并”在一塊呢?使用 merge()函數(shù)吧。我們用ASCII圖表來(lái)解釋這個(gè)函數(shù)的作用:

流 A: ---a--------e-----o----->
流 B: -----B---C-----D-------->
          vvvvvvvvv merge vvvvvvvvv
          ---a-B---C--e--D--o----->

使用merge()后簡(jiǎn)單多了:

var requestOnRefreshStream = refreshClickStream
  .map(function() {
    var randomOffset = Math.floor(Math.random()*500);
    return "https://api.github.com/users?since=" + randomOffset;
  });
  
var startupRequestStream = Rx.Observable.just("https://api.github.com/users");

var requestStream = Rx.Observable.merge(
  requestOnRefreshStream, startupRequestStream
);

如果不需要requestOnRefreshStream、startupRequestStream這兩個(gè)中間流,寫(xiě)法更干凈、簡(jiǎn)潔。

var requestStream = refreshClickStream
  .map(function() {
    var randomOffset = Math.floor(Math.random()*500);
    return "https://api.github.com/users?since=" + randomOffset;
  })
  .merge(Rx.Observable.just("https://api.github.com/users"));

還能更簡(jiǎn)單,更有可讀性:

var requestStream = refreshClickStream
  .map(function() {
    var randomOffset = Math.floor(Math.random()*500);
    return "https://api.github.com/users?since=" + randomOffset;
  })
  .startWith("https://api.github.com/users");

startWith() 函數(shù)的作用和它的命名一樣。 無(wú)論是什么樣的流,startWith(x) 都會(huì)把x作為這個(gè)流的啟示輸入并發(fā)射出來(lái)。 上面的實(shí)現(xiàn),還不夠DRY(Don"t repeat yourself,不要重復(fù)!),API請(qǐng)求的URL地址重復(fù)了兩遍。我們將 startWith() 緊接在refreshClickStream之后,在頁(yè)面打開(kāi)后就模擬一次點(diǎn)擊。

var requestStream = refreshClickStream.startWith("startup click")
  .map(function() {
    var randomOffset = Math.floor(Math.random()*500);
    return "https://api.github.com/users?since=" + randomOffset;
  });

Nice!事情不會(huì)被搞砸了,startWith()完美解決了問(wèn)題。

3位“你可能感興趣的用戶”的流的構(gòu)建

目前為止,僅僅在訂閱(subscribe())時(shí),你會(huì)觸及到“感興趣的用戶”區(qū)塊的渲染。但是通過(guò)刷新按鈕,問(wèn)題接踵而至:你點(diǎn)擊了刷新按鈕,在新的響應(yīng)到達(dá)之前,原來(lái)的“你可能感興趣的”3個(gè)用戶并不會(huì)馬上消失。為了增強(qiáng)用戶體驗(yàn),我們希望在用戶點(diǎn)擊了刷新按鈕后就清楚老數(shù)據(jù)。

refreshClickStream.subscribe(function() {
  // 清楚舊數(shù)據(jù): 3個(gè)你可能感興趣的用戶的DOM元素
});

停!不要用力過(guò)猛。兩個(gè) 訂閱行為都會(huì)影響到這個(gè)區(qū)塊的渲染。(responseStream.subscribe()、refreshClickStream.subscribe()),并且上面的設(shè)計(jì)也不符合關(guān)注分離的理念。還記得RP 神馬都是流 的箴言嗎?

那么開(kāi)始構(gòu)建這個(gè)專門的推薦流:流會(huì)發(fā)射“你可能感興趣的用戶”的JSON對(duì)象。我們會(huì)分別構(gòu)建三種這樣的流,第一種長(zhǎng)這個(gè)樣:

var suggestion1Stream = responseStream
  .map(function(listUsers) {
    // 隨機(jī)從列表中取出一個(gè)用戶
    return listUsers[Math.floor(Math.random()*listUsers.length)];
  });

另外兩個(gè)流suggestion2Streamsuggestion3Stream復(fù)制粘貼就好啦。呃。。。DRY不要重復(fù),我把這個(gè)問(wèn)題作為這個(gè)教程的聯(lián)系,自己做一遍你會(huì)去思考這類場(chǎng)景中如何避免代碼的重復(fù)。

譯者注:如果使用UnderScore,一種方法是,新的方法總是會(huì)返回JSON Object數(shù)組:

var suggestionStream = responseStream
  .map(suggestionN(listUsers, n));

function suggestionN(listUsers, n) {
    _.times(n, function() {
        return listUsers[Math.floor(Math.random()*listUsers.length)];
    })
}

我們不再訂閱響應(yīng)流,而是變更為:

suggestion1Stream.subscribe(function(suggestion) {
  // 在區(qū)塊中渲染1位用戶的DOM元素
});

回到原始需求:“每一次刷新后,清除原來(lái)的用戶”,我們可以在刷新后,返回null作為推薦流:

var suggestion1Stream = responseStream
  .map(function(listUsers) {
    // 隨機(jī)從列表中取出一個(gè)用戶
    return listUsers[Math.floor(Math.random()*listUsers.length)];
  })
  .merge(
    refreshClickStream.map(function(){ return null; })
  );

在渲染環(huán)節(jié),null代表無(wú)數(shù)據(jù),我們就隱藏之前的DOM元素。

suggestion1Stream.subscribe(function(suggestion) {
  if (suggestion === null) {
    // 在區(qū)塊中隱藏一個(gè)推薦用戶的DOM元素
  }
  else {
    // 在區(qū)塊中渲染一個(gè)推薦用戶的DOM元素
  }
});

整個(gè)事件流如圖所示:

  刷新按鈕流: ----------o--------o---->
     請(qǐng)求流: -r--------r--------r---->
     響應(yīng)流: ----R---------R------R-->   
 推薦1個(gè)用戶: ----s-----N---s----N-s-->

N 表示 null.

頁(yè)面打開(kāi)后,我們渲染“空”推薦區(qū)塊,可以通過(guò)在推薦流中附加startWith(null)實(shí)現(xiàn):

var suggestion1Stream = responseStream
  .map(function(listUsers) {
    // 隨機(jī)從列表中取出一個(gè)用戶
    return listUsers[Math.floor(Math.random()*listUsers.length)];
  })
  .merge(
    refreshClickStream.map(function(){ return null; })
  )
  .startWith(null);

Which results in:

   刷新按鈕流: ----------o---------o---->
      請(qǐng)求流: -r--------r---------r---->
      響應(yīng)流: ----R----------R------R-->   
 推薦1個(gè)用戶:  -N--s-----N----s----N-s-->
關(guān)閉一個(gè)推薦元素,從緩存獲得新的推薦元素

最后一個(gè)需要實(shí)現(xiàn)的功能是:點(diǎn)擊"x"按鈕后關(guān)閉當(dāng)前的推薦元素,載入一個(gè)新的數(shù)據(jù)并渲染。拍腦袋意向,無(wú)論點(diǎn)擊了啥按鈕,我們重新請(qǐng)求一次新數(shù)據(jù),生成一個(gè)新的響應(yīng)流就好了:

var close1Button = document.querySelector(".close1");
var close1ClickStream = Rx.Observable.fromEvent(close1Button, "click");
// close2Button 和 close3Button 作為練習(xí)

var requestStream = refreshClickStream.startWith("startup click")
  .merge(close1ClickStream) // 加上這個(gè)
  .map(function() {
    var randomOffset = Math.floor(Math.random()*500);
    return "https://api.github.com/users?since=" + randomOffset;
  });

擦,點(diǎn)擊了關(guān)閉按鈕整個(gè)推薦區(qū)塊都被刷新了!看來(lái)我們只有使用原來(lái)的相應(yīng)流才能解決這個(gè)bug,況且每次慷慨大方的GitHub給我們100個(gè)用戶的數(shù)據(jù),我們只使用3個(gè),還有1大堆留著等我們用呢,沒(méi)有必要再請(qǐng)求更多的數(shù)據(jù)了。

讓我們從流的角度思考,當(dāng)點(diǎn)擊"x"事件發(fā)生后,我們使用 最近一次的相應(yīng)流 并從中隨機(jī)取出用戶就好了:

      請(qǐng)求流: --r--------------->
      響應(yīng)流: ------R----------->
   點(diǎn)擊關(guān)閉流: ------------c----->
推薦1個(gè)用戶流: ------s-----s----->

在Rx*框架中,一個(gè)使用函數(shù)叫 combineLatest 。 函數(shù)將兩個(gè)流作為輸入,并且當(dāng)其中任意一個(gè)流發(fā)射之后, combineLatest 都會(huì)組合兩個(gè)流中最新的值 ab然后輸出一個(gè)新的流,流的值為 c = f(x,y) 其中 f(x, y) 是傳入的自定義函數(shù),配合上時(shí)序圖更好理解:

流 A:     --a-----------e--------i-------->
流 B:     -----b----c--------d-------q---->
          vvvvvvvv combineLatest(f) vvvvvvv
          ----AB---AC--EC---ED--ID--IQ---->

這里的函數(shù)f,將輸入的字符串變?yōu)榇髮?xiě)

現(xiàn)在我們?cè)?close1ClickStreamresponseStream使用combineLatest() , 只要用戶點(diǎn)擊關(guān)閉按鈕,我們就結(jié)合最新的響應(yīng)流來(lái)產(chǎn)生suggestion1Stream。 另一個(gè)方面,combineLatest() 是一個(gè)同步操作:每當(dāng)新的響應(yīng)流發(fā)射了值, 同樣會(huì)結(jié)合 close1ClickStream產(chǎn)生新的推薦數(shù)據(jù)。這樣我們大大簡(jiǎn)化了suggestion1Stream

var suggestion1Stream = close1ClickStream
  .combineLatest(responseStream,             
    function(click, listUsers) {
      return listUsers[Math.floor(Math.random()*listUsers.length)];
    }
  )
  .merge(
    refreshClickStream.map(function(){ return null; })
  )
  .startWith(null);

最后還有一點(diǎn)點(diǎn)問(wèn)題:combineLatest()需要結(jié)合傳入的兩個(gè)流,如果其中一個(gè)流從未發(fā)射過(guò)任何值,combineLatest()將不會(huì)輸入任何新的流?;仡櫼幌律厦娴腁SCII圖表,當(dāng)?shù)谝粋€(gè)流發(fā)射值a時(shí),不會(huì)有任何輸出,僅當(dāng)?shù)诙€(gè)流也發(fā)射了值b后,combineLatest()才會(huì)開(kāi)始向外輸出。

解決方法很多,我們采取最簡(jiǎn)單的方式(上面例子也用到過(guò)),我們?cè)陧?yè)面打開(kāi)時(shí)限模擬一次關(guān)閉按鈕的點(diǎn)擊:

var suggestion1Stream = close1ClickStream.startWith("startup click") // we added this
  .combineLatest(responseStream,             
    function(click, listUsers) {
      return listUsers[Math.floor(Math.random()*listUsers.length)];
    }
  )
  .merge(
    refreshClickStream.map(function(){ return null; })
  )
  .startWith(null);
總結(jié)

再M(fèi)ark一下當(dāng)前的代碼,是不是很有成就感:

var refreshButton = document.querySelector(".refresh");
var refreshClickStream = Rx.Observable.fromEvent(refreshButton, "click");

var closeButton1 = document.querySelector(".close1");
var close1ClickStream = Rx.Observable.fromEvent(closeButton1, "click");
// close2 和 close3 作為練習(xí)

var requestStream = refreshClickStream.startWith("startup click")
  .map(function() {
    var randomOffset = Math.floor(Math.random()*500);
    return "https://api.github.com/users?since=" + randomOffset;
  });

var responseStream = requestStream
  .flatMap(function (requestUrl) {
    return Rx.Observable.fromPromise($.ajax({url: requestUrl}));
  });

var suggestion1Stream = close1ClickStream.startWith("startup click")
  .combineLatest(responseStream,             
    function(click, listUsers) {
      return listUsers[Math.floor(Math.random()*listUsers.length)];
    }
  )
  .merge(
    refreshClickStream.map(function(){ return null; })
  )
  .startWith(null);
// suggestion2Stream 和 suggestion3Stream 作為練習(xí)

suggestion1Stream.subscribe(function(suggestion) {
  if (suggestion === null) {
    // 隱藏一個(gè)用戶的DOM元素
  }
  else {
    // 渲染一個(gè)新的推薦用戶的DOM元素
  }
});

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/78467.html

相關(guān)文章

  • 【響應(yīng)式編程的思維藝術(shù)】 (1)Rxjs專題學(xué)習(xí)計(jì)劃

    摘要:由于技術(shù)棧的學(xué)習(xí),筆者需要在原來(lái)函數(shù)式編程知識(shí)的基礎(chǔ)上,學(xué)習(xí)的使用。筆者在社區(qū)發(fā)現(xiàn)了一個(gè)非常高質(zhì)量的響應(yīng)式編程系列教程共篇,從基礎(chǔ)概念到實(shí)際應(yīng)用講解的非常詳細(xì),有大量直觀的大理石圖來(lái)輔助理解流的處理,對(duì)培養(yǎng)響應(yīng)式編程的思維方式有很大幫助。 showImg(https://segmentfault.com/img/bVus8n); [TOC] 一. 響應(yīng)式編程 響應(yīng)式編程,也稱為流式編程...

    lscho 評(píng)論0 收藏0
  • RxJS基礎(chǔ)教程

    摘要:是一個(gè)基于可觀測(cè)數(shù)據(jù)流在異步編程應(yīng)用中的庫(kù)。正如官網(wǎng)所說(shuō),是基于觀察者模式,迭代器模式和函數(shù)式編程。它具有時(shí)間與事件響應(yīng)的概念。通知不再發(fā)送任何值。和通知可能只會(huì)在執(zhí)行期間發(fā)生一次,并且只會(huì)執(zhí)行其中的一個(gè)。 RxJS是一個(gè)基于可觀測(cè)數(shù)據(jù)流在異步編程應(yīng)用中的庫(kù)。 ReactiveX is a combination of the best ideas fromthe Observer p...

    defcon 評(píng)論0 收藏0
  • 學(xué)習(xí)實(shí)踐 - 收藏集 - 掘金

    摘要:官網(wǎng)地址聊天機(jī)器人插件開(kāi)發(fā)實(shí)例教程一創(chuàng)建插件在系統(tǒng)技巧使你的更加專業(yè)前端掘金一個(gè)幫你提升技巧的收藏集。我會(huì)簡(jiǎn)單基于的簡(jiǎn)潔視頻播放器組件前端掘金使用和實(shí)現(xiàn)購(gòu)物車場(chǎng)景前端掘金本文是上篇文章的序章,一直想有機(jī)會(huì)再次實(shí)踐下。 2道面試題:輸入U(xiǎn)RL按回車&HTTP2 - 掘金通過(guò)幾輪面試,我發(fā)現(xiàn)真正那種問(wèn)答的技術(shù)面,寫(xiě)一堆項(xiàng)目真不如去刷技術(shù)文章作用大,因此刷了一段時(shí)間的博客和掘金,整理下曾經(jīng)被...

    mikyou 評(píng)論0 收藏0
  • RxJS API解析(一)

    摘要:選擇后,僅有聯(lián)通的可觀察對(duì)象會(huì)被觀察到。從外部看,所有訂閱者僅能觀測(cè)到這個(gè)聯(lián)通了支流。,其中表示輸入流,是操作符,是最后的輸出流。截圖驗(yàn)證一下當(dāng)一個(gè)流被聯(lián)通后,其他的流腫么辦先記住結(jié)論未被選擇的流將被調(diào)用方法,也就是說(shuō),他們被終止了。 起因 在SegmentFault里發(fā)布過(guò)一篇RxJS的簡(jiǎn)明教程,很多人反饋對(duì)這個(gè)主題很是很感興趣,詳見(jiàn)RxJS簡(jiǎn)明教程。 Rx 是一種編程的思維,而不是...

    姘擱『 評(píng)論0 收藏0
  • ionic3 教程(五)基本的網(wǎng)絡(luò)請(qǐng)求

    摘要:鏈接教程一安裝和配置教程二登錄頁(yè)制作教程三設(shè)置頁(yè)制作教程四安卓硬件返回鍵處理教程五基本的網(wǎng)絡(luò)請(qǐng)求這是最后一節(jié),本節(jié)主要用最簡(jiǎn)單網(wǎng)絡(luò)請(qǐng)求和基本的內(nèi)置指令做一個(gè)演示。接收數(shù)據(jù)用依賴注入網(wǎng)絡(luò)請(qǐng)求會(huì)返回一個(gè)對(duì)象。 showImg(https://segmentfault.com/img/remote/1460000010805290); 鏈接: ionic3教程(一)安裝和配置 ionic...

    bbbbbb 評(píng)論0 收藏0
  • ionic3 教程(五)基本的網(wǎng)絡(luò)請(qǐng)求

    摘要:鏈接教程一安裝和配置教程二登錄頁(yè)制作教程三設(shè)置頁(yè)制作教程四安卓硬件返回鍵處理教程五基本的網(wǎng)絡(luò)請(qǐng)求這是最后一節(jié),本節(jié)主要用最簡(jiǎn)單網(wǎng)絡(luò)請(qǐng)求和基本的內(nèi)置指令做一個(gè)演示。接收數(shù)據(jù)用依賴注入網(wǎng)絡(luò)請(qǐng)求會(huì)返回一個(gè)對(duì)象。 showImg(https://segmentfault.com/img/remote/1460000010805290); 鏈接: ionic3教程(一)安裝和配置 ionic...

    MudOnTire 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<