聊聊前端數(shù)據(jù)流選型
1. 什么是數(shù)據(jù)流
什么是流?
在數(shù)學(xué)中,一個流用數(shù)學(xué)方式形式化了“取決于時間的變化”的一般想法。暫且定義流是響應(yīng)時間變化的一個集合體。
什么是數(shù)據(jù)流
依據(jù)之前的定義,數(shù)據(jù)流即隨時間變化的一個數(shù)據(jù)集合。
前端針對于現(xiàn)狀mvvm模式下,數(shù)據(jù)即頁面,在多數(shù)情況下,數(shù)據(jù)不變頁面不變。那么我們轉(zhuǎn)換一下,數(shù)據(jù)==>頁面,數(shù)據(jù)流是否可以等價為一個頁面的變化集合。這個是什么,就是我們的業(yè)務(wù)邏輯。
當(dāng)然,前面的假設(shè),是經(jīng)過很多轉(zhuǎn)化,其實有很多漏洞和錯誤。但是可以作為一個簡單的參考。
這個不是數(shù)據(jù)流的定義,只是作為一個引子思考。
2. 現(xiàn)行前端數(shù)據(jù)管理模式
現(xiàn)行三大數(shù)據(jù)管理方式
- 函數(shù)式、不可變、模式化。典型實現(xiàn):Redux。
- 響應(yīng)式、依賴追蹤。典型實現(xiàn):Mobx。
- 響應(yīng)式,以流的形式實現(xiàn)。Rxjs、xstream。
Redux模式(reduck)
redux模式常規(guī)用法是作為整個應(yīng)用全局狀態(tài)管理使用。這只是作為一個提高跨組件通信的能力的工具。redux的思想是作為獨立于組件的一個數(shù)據(jù)倉庫,對數(shù)據(jù)進行保護,保障數(shù)據(jù)穩(wěn)定可靠。
可以簡單理解redux是一個帶保護的全局使用的Context(useContext)。
針對于原生的提供了數(shù)據(jù)保護(dispatch+reducer),對于更改只允許使用dispatch進行更改。能夠保障可回溯性,數(shù)據(jù)來源清晰,能夠十分良好的隔絕副作用。
使用方面:
- 優(yōu)點:數(shù)據(jù)隔離,數(shù)據(jù)變化可溯源。
- 缺點:多redux直接完全隔離,小型化困難,action方法容易膨脹。
- 業(yè)務(wù)方面使用:對于大型項目拆分設(shè)計概念不足,store數(shù)據(jù)個人管控不友好,容易造成理解困難邏輯修改困難。
Mobx模式
mobx引入了全新的思想,將數(shù)據(jù)作為一個源頭,擁有當(dāng)數(shù)據(jù)變化時,通過計算狀態(tài),頁面進行變化,并且根據(jù)observable,自動根據(jù)依賴執(zhí)行更新。雖然有了action,但還是沒有強制分離副作用。
mobx就好像將數(shù)據(jù)和組件進行綁定,形成依賴關(guān)系,自動訂閱和自動發(fā)布,狀態(tài)變更組件就變更。將邏輯和視圖直接綁定在一起,這本應(yīng)該是十分高效的情況,但是因為深入了組件,副作用的處理還是不夠清晰,對于個人把控還是不夠友好。
流模式(rxjs)
rxjs和mobx有些相似。rxjs將所有的數(shù)據(jù)都可以隨意的拆散和組合成一個新的節(jié)點,可以簡單理解為將redux的state進行了打散成多個數(shù)據(jù)節(jié)點,每一個任意節(jié)點都可以進行類似computed的計算生成新的節(jié)點。
流模式相較于redux模式?jīng)]有action的規(guī)范,卻定義了更改的節(jié)點范圍,只能更改定義的入口節(jié)點(一條流的起始節(jié)點)。rxjs沒有mobx從數(shù)據(jù)變化到頁面變化這個功能,可以使用useState和useEffect實現(xiàn)或者現(xiàn)成的三方庫rxjs-hooks。
rxjs的優(yōu)勢是,抽離所有數(shù)據(jù)源之后,剩余全部都是邏輯問題,副作用在抽離數(shù)據(jù)源的時候就已經(jīng)剝離干凈了(因為外部副作用數(shù)據(jù)也可以抽離成rxjs的節(jié)點),剩下就通過api和純函數(shù)來編寫具體的邏輯了。
又因為大量的api,拆解observable節(jié)點的成本極低,所以邏輯拆分十分容易,可讀性十分高。
rxjs有推和拉的概念,在正常邏輯十分流暢的情況下,程序的代碼應(yīng)該是每個節(jié)點轉(zhuǎn)變都會推動下一個節(jié)點的執(zhí)行。在rxjs中將數(shù)據(jù)流進行串流好后,組件只要對于頭部節(jié)點進行讀寫數(shù)據(jù),對于尾部節(jié)點直接讀取數(shù)據(jù)就可以,大部分邏輯全部被抽離出了組件。
使用方面:
- 優(yōu)點:邏輯拆分簡便,純函數(shù)式編程。
- 缺點:數(shù)據(jù)拆分復(fù)雜,重設(shè)計,api學(xué)習(xí)和理解成本高。
理想情況下,頁面就是一個個無狀態(tài)組件,行為改變數(shù)據(jù)。數(shù)據(jù)變化又觸發(fā)邏輯變更,邏輯變更數(shù)據(jù)。數(shù)據(jù)又回流到頁面,這是一個整體的閉環(huán),以數(shù)據(jù)為核心,完美的做到數(shù)據(jù)驅(qū)動頁面。
新星 Recoil
recoil是facebook官方推薦的一個狀態(tài)管理庫,作為一個“新成員”,recoil相比于之前的三種狀態(tài)管理方式,做了很多取舍。它有節(jié)點的概念,有atom(原子數(shù)據(jù))和selector(派生數(shù)據(jù))但是不和mobx一樣,recoil是基于Immutable(不變)模式。
recoil的基礎(chǔ)思想是atom數(shù)據(jù)之間沒有關(guān)聯(lián),產(chǎn)生的關(guān)聯(lián)數(shù)據(jù)全部由selector來產(chǎn)生,atom的變動,相關(guān)的selector隨之變動,這個和響應(yīng)式流的思想一致的。
recoil的優(yōu)勢,貼合react,可以將recoil的實現(xiàn)當(dāng)作通過useMemo包裝的context,api使用可以滿足只讀,只寫進行拆分,可以十分貼合最優(yōu)渲染,降低無用的渲染。
上述前三種數(shù)據(jù)流沒有什么優(yōu)劣好壞之分,只是在不同場景中使用各有各的優(yōu)勢而已。
3. 理想中的源數(shù)據(jù)編程
數(shù)據(jù)與數(shù)據(jù)之間的關(guān)系
數(shù)據(jù)不是憑空產(chǎn)生的數(shù)據(jù),數(shù)據(jù)可能又會產(chǎn)生新的數(shù)據(jù)。
數(shù)據(jù)之間推行的是最小可用原則,分而治之,這才更利于我們開發(fā)和維護。
個人把產(chǎn)生數(shù)據(jù)的起始數(shù)據(jù)定義為源數(shù)據(jù)。有些數(shù)據(jù)可以互相轉(zhuǎn)換,那如何定義源數(shù)據(jù)???
定義源數(shù)據(jù)
從組件(頁面)視角看一下數(shù)據(jù)。
- 面向接口編程
大部分情況下前端和后端之間的數(shù)據(jù)交互就只有接口這一種。又因為真實的所有數(shù)據(jù)都是從服務(wù)端獲取的數(shù)據(jù),所以下意識的以服務(wù)端接口為數(shù)據(jù)起始。數(shù)據(jù)處理的鏈路較長,范圍變大對于個人的理解要求是十分高的,個人認為這對于一個大型應(yīng)用是不健康的。
- 面向數(shù)據(jù)編程
分離接口請求,只關(guān)心組件狀態(tài),對于組件方面,任何數(shù)據(jù)不將其做區(qū)分,數(shù)據(jù)來就渲染。
個人將用戶可交互的數(shù)據(jù)可以定義為源數(shù)據(jù)。因為前端接口請求也是由用戶的信息請求來的。對于應(yīng)用來說,只有用戶的操作不可預(yù)知,其余操作都是可控的。
如果將可控的邏輯封裝后抽離,管理的時候不需要再直接感知到這些,我們直面的就是用戶的操作和頁面的響應(yīng)。
例如: 以單個列表頁來說,用戶選擇的篩選項就是源數(shù)據(jù),而接口請求回來的列表數(shù)據(jù)就是派生數(shù)據(jù),由接口請求產(chǎn)生的頁面loading態(tài)也可以是派生數(shù)據(jù)也可以是源數(shù)據(jù)。
redux數(shù)據(jù)模型
在使用redux的時候沒有很好的辦法處理數(shù)據(jù)的層級關(guān)系,導(dǎo)致store中數(shù)據(jù)的池子越來越大,沒有很強分層的概念,這也是redux小型化困難帶來的,使用的時候會下意識將跨層級的數(shù)據(jù)存入store。
其次redux沒法很好的描述數(shù)據(jù)與數(shù)據(jù)之間的關(guān)系,有人說computed可以描述數(shù)據(jù)與數(shù)據(jù)之間的關(guān)聯(lián),簡單意義上是沒問題的,但是computed的局限性,跨redux無法支撐,如果要使用,必須將所有源數(shù)據(jù)匯集到同一個redux之間,這與最小可用原則是相違背的。
就是因為redux的設(shè)計模式不夠靈活,導(dǎo)致會將大量數(shù)據(jù)與數(shù)據(jù)之間的轉(zhuǎn)化邏輯積壓在頁面或者組件內(nèi)部,這對于視圖層是一種負擔(dān)。
響應(yīng)式流數(shù)據(jù)模型
為了方便理解,可以和之前一樣將數(shù)據(jù)處理理解為computed,每一個節(jié)點都可以隨意衍生出一個新的節(jié)點,但是觸發(fā)整條流的變化又只會在初始節(jié)點(源數(shù)據(jù))節(jié)點,使用整條流的結(jié)果。
在流模式中對于組件或頁面外層的數(shù)據(jù)沒有任何層次之分,每個節(jié)點都是平級的,如果分層,可以在業(yè)務(wù)上分層,通過不斷的拼接,將業(yè)務(wù)邏輯進行串聯(lián),得出你想要的結(jié)果,相較于原先散亂在各地的邏輯,串聯(lián)的流式邏輯在可讀性上也更優(yōu)。
流式數(shù)據(jù)的優(yōu)點是拆分成本極低,這樣也更符合我們的思想,代碼塊拆分,這樣每一小塊邏輯拆分出一個節(jié)點,邏輯復(fù)雜度就通過不斷的拆解,變得十分低了,但是又不會因為拆解的過多,邏輯散亂。
- 沒有固化數(shù)據(jù)的層級,離散的數(shù)據(jù),可以自由定義和拼接。
- 以離散的數(shù)據(jù),將邏輯串流,數(shù)據(jù)靈活之后,邏輯卻是收斂,不會分散,做到了數(shù)據(jù)精準的使用。
reducx和rxjs的數(shù)據(jù)管理范圍
總結(jié)
綜上所述,其實不同的模式帶來的是針對不同場景的應(yīng)用,redux的快捷應(yīng)用,快捷開發(fā),數(shù)據(jù)變化的穩(wěn)定,mobx對于響應(yīng)式的變化,都是各有各的特色。
在寫代碼的時候,我的感覺像是在構(gòu)建一個動畫的每一幀(視圖),又要給出每一幀為什么變化(寫邏輯,事件)的感覺,邏輯和視圖混合在一起,對于整體的把控十分難處理,就像你需要對于整個動畫的變化都掌握,抽離了視圖和邏輯,邏輯只需要變化數(shù)據(jù),視圖只需要針對對應(yīng)的數(shù)據(jù)變化而已。
參考文章
流動的數(shù)據(jù)——使用 RxJS 構(gòu)造復(fù)雜單頁應(yīng)用的數(shù)據(jù)邏輯
精讀《前端數(shù)據(jù)流哲學(xué)》