您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關RxJS有什么用的內容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。
RxJS簡介
通常,對RxJS的解釋會是這么一些東西,我們來分別看看它們的含義是什么。
Reactive
Lodash for events
Observable
Stream-based
什么是Reactive呢,一個比較直觀的對比是這樣的:
比如說,abc三個變量之間存在加法關系:
a = b + c
在傳統方式下,這是一種一次性的賦值過程,調用一次就結束了,后面b和c再改變,a也不會變了。
而在Reactive的理念中,我們定義的不是一次性賦值過程,而是可重復的賦值過程,或者說是變量之間的關系:
a: = b + c
定義出這種關系之后,每次b或者c產生改變,這個表達式都會被重新計算。不同的庫或者語言的實現機制可能不同,寫法也不完全一樣,但理念是相通的,都是描述出數據之間的聯動關系。
在前端,我們通常有這么一些方式來處理異步的東西:
回調
事件
Promise
Generator
其中,存在兩種處理問題的方式,因為需求也是兩種:
分發
流程
在處理分發的需求的時候,回調、事件或者類似訂閱發布這種模式是比較合適的;而在處理流程性質的需求時,Promise和Generator比較合適。
在前端,尤其交互很復雜的系統中,RxJS其實是要比Generator有優勢的,因為常見的每種客戶端開發都是基于事件編程的,對于事件的處理會非常多,而一旦系統中大量出現一個事件要修改視圖的多個部分(狀態樹的多個位置),分發關系就更多了。
RxJS的優勢在于結合了兩種模式,它的每個Observable上都能夠訂閱,而Observable之間的關系,則能夠體現流程(注意,RxJS里面的流程的控制和處理,其直觀性略強于Promise,但弱于Generator)。
我們可以把一切輸入都當做數據流來處理,比如說:
用戶操作
網絡響應
定時器
Worker
RxJS提供了各種API來創建數據流:
單值:of, empty, never
多值:from
定時:interval, timer
從事件創建:fromEvent
從Promise創建:fromPromise
自定義創建:create
創建出來的數據流是一種可觀察的序列,可以被訂閱,也可以被用來做一些轉換操作,比如:
改變數據形態:map, mapTo, pluck
過濾一些值:filter, skip, first, last, take
時間軸上的操作:delay, timeout, throttle, debounce, audit, bufferTime
累加:reduce, scan
異常處理:throw, catch, retry, finally
條件執行:takeUntil, delayWhen, retryWhen, subscribeOn, ObserveOn
轉接:switch
也可以對若干個數據流進行組合:
concat,保持原來的序列順序連接兩個數據流
merge,合并序列
race,預設條件為其中一個數據流完成
forkJoin,預設條件為所有數據流都完成
zip,取各來源數據流最后一個值合并為對象
combineLatest,取各來源數據流最后一個值合并為數組
這時候回頭看,其實RxJS在事件處理的路上已經走得太遠了,從事件到流,它被稱為lodash for events,倒不如說是lodash for stream更貼切,它提供的這些操作符也確實可以跟lodash媲美。
數據流這個詞,很多時候,是從data-flow翻譯過來的,但flow跟stream是不一樣的,我的理解是:flow只關注一個大致方向,而stream是受到更嚴格約束的,它更像是在無形的管道里面流動。
那么,數據的管道是什么形狀的?
在RxJS中,存在這么幾種東西:
Observable 可觀察序列,只出不進
Observer 觀察者,只進不出
Subject 可出可進的可觀察序列,可作為觀察者
ReplaySubject 帶回放
Subscription 訂閱關系
前三種東西,根據它們數據進出的可能性,可以通俗地理解他們的連接方式,這也就是所謂管道的“形狀”,一端密閉一端開頭,還是兩端開口,都可以用來輔助記憶。
上面提到的Subscription,則是訂閱之后形成的一個訂閱關系,可以用于取消訂閱。
下面,我們通過一些示例來大致了解一下RxJS所提供的能力,以及用它進行開發所需要的思路轉換。
示例一:簡單的訂閱
很多時候,我們會有一些顯示時間的場景,比如在頁面下添加評論,評論列表中顯示了它們分別是什么時間創建的,為了含義更清晰,可能我們會引入moment這樣的庫,把這個時間轉換為與當前時間的距離:
const diff = moment(createAt).fromNow()
這樣,顯示的時間就是:一分鐘內,昨天,上個月這樣的字樣。
但我們注意到,引入這個轉換是為了增強體驗,而如果某個用戶停留在當前視圖時間太長,它的這些信息會變得不準確,比如說,用戶停留了一個小時,而它看到的信息還顯示:5分鐘之前發表了評論,實際時間是一個小時零5分鐘以前的事了。
從這個角度看,我們做這個體驗增強的事情只做了一半,不準確的信息是不能算作增強體驗的。
在沒有RxJS的情況下,我們可能會通過一個定時器來做這件事,比如在組件內部:
tick() { this.diff = moment(createAt).fromNow() setTimeout(tick.bind(this), 1000) }
但組件并不一定只有一份實例,這樣,整個界面上可能就有很多定時器在同時跑,這是一種浪費。如果要做優化,可以把定時器做成一種服務,把業務上需要周期執行的東西放進去,當作定時任務來跑。
如果使用RxJS,可以很容易做到這件事:
Observable.interval(1000).subscribe(() => { this.diff = moment(createAt).fromNow() })
示例二:對時間軸的操縱
RxJS一個很強大的特點是,它以流的方式來對待數據,因此,可以用一些操作符對整個流上所有的數據進行延時、取樣、調整密集度等等。
const timeA$ = Observable.interval(1000) const timeB$ = timeA$.filter(num => { return (num % 2 != 0) && (num % 3 != 0) && (num % 5 != 0) && (num % 7 != 0) }) const timeC$ = timeB$.debounceTime(3000) const timeD$ = timeC$.delay(2000)
示例代碼中,我們創建了四個流:
A是由定時器產生的,每秒一個值
B從A里面過濾掉了一些
C在B的基礎上,對每兩個間距在3秒之內的值進行了處理,只留下后一個值
D把C的結果整體向后平移了2秒
所以結果大致如下:
A: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
B: 1 11 13 17 19
C: 1 13 19
D: 1 13
示例三:我們來晚了
RxJS還提供了BehaviourSubject和ReplaySubject這樣的東西,用于記錄數據流上一些比較重要的信息,讓那些“我們來晚了”的訂閱者們回放之前錯過的一切。
ReplaySubject可以指定保留的值的個數,超過的部分會被丟棄。
最近新版《射雕英雄傳》比較火,我們來用代碼描述其中一個場景。
郭靖和黃蓉一起背書,黃蓉記憶力很好,看了什么,就全部記得;而郭靖屬魚的,記憶只有七秒,始終只記得背誦的最后三個字,兩人一起背誦《九陰真經》。
代碼實現如下:
const 九陰真經 = '天之道,損有余而補不足' const 黃蓉$ = new ReplaySubject(Number.MAX_VALUE) const 郭靖$ = new ReplaySubject(3) const 讀書$ = Observable.from(九陰真經.split('')) 讀書$.subscribe(黃蓉$) 讀書$.subscribe(郭靖$)
執行之后,我們就可以看到,黃蓉背出了所有字,郭靖只記得“補不足”三個字。
示例四:自動更新的狀態樹
熟悉Redux的人應該會對這樣一套理念不陌生:
當前視圖狀態 := 之前的狀態 + 本次修改的部分
從一個應用啟動之后,整個全局狀態的變化,就等于初始的狀態疊加了之后所有action導致的狀態修改結果。
所以這就是一個典型的reduce操作。在RxJS里面,有一個scan操作符可以用來表達這個含義,比如說,我們可以表達這樣一個東西:
const action$ = new Subject() const reducer = (state, payload) => { // 把payload疊加到state上返回 } const state$ = action$.scan(reducer) .startWith({})
只需往這個action$里面推action,就能夠在state$上獲取出當前狀態。
在Redux里面,會有一個東西叫combineReducer,在state比較大的時候,用不同的reducer修改state的不同的分支,然后合并。如果使用RxJS,也可以很容易表達出來:
const meAction$ = new Subject() const meReducer = (state, payload) => {} const articleAction$ = new Subject() const articleReducer = (state, payload) => {} const me$ = meAction$.scan(meReducer).startWith({}) const article$ = articleAction$.scan(articleReducer).startWith({}) const state$ = Observable .zip( me$, article$, (me, article) => {me, article} )
借助這樣的機制,我們實現了Redux類似的功能,社區里面也有基于RxJS實現的Redux-Observable這樣的Redux中間件。
注意,我們這里的代碼中,并未使用dispatch action這樣的方式去嚴格模擬Redux。
再深入考慮,在比較復雜的場景下,reducer其實很復雜。比如說,視圖上發起一個操作,會需要修改視圖的好多地方,因此也就是要修改全局狀態樹的不同位置。
在這樣的場景中,從視圖發起的某個action,要么調用一個很復雜的reducer去到處改數據,要么再次發起多個action,讓很多個reducer各自改自己的數據。
前者的問題是,代碼耦合太嚴重;后者的問題是,整個流程太難追蹤,比如說,某一塊狀態,想要追蹤到自己是被從哪里發起的修改所改變的,是非常困難的事情。
如果我們能夠把Observable上面的同步修改過程視為reducer,就可以從另外一些角度大幅簡化代碼,并且讓聯動邏輯清晰化。例如,如果我們想描述一篇文章的編輯權限:
const editable$ = Observable.combineLatest(article$, me$) .map(arr => { let [article, me] = arr return me.isAdmin || article.author === me.id })
這段代碼的實質是什么?其實本質上還是reducer,表達的是數據的合并與轉換過程,而且是同步的。我們可以把article和me的變更reduce到article$和me$里,由它們派發隱式的action去推動editable計算新值。
感謝各位的閱讀!關于“RxJS有什么用”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。