摘要:隨著前端應(yīng)用的復(fù)雜度越來越高,如何管理應(yīng)用的數(shù)據(jù)已經(jīng)是一個不可回避的問題。應(yīng)用的數(shù)據(jù)不是只有狀態(tài)的,還有事件異步常量等等。出于以上兩點原因,最終決定基于來設(shè)計一套管理應(yīng)用的狀態(tài)的解決方案。
隨著前端應(yīng)用的復(fù)雜度越來越高,如何管理應(yīng)用的數(shù)據(jù)已經(jīng)是一個不可回避的問題。當(dāng)你面對的是業(yè)務(wù)場景復(fù)雜、需求變動頻繁、各種應(yīng)用數(shù)據(jù)互相關(guān)聯(lián)依賴的大型前端應(yīng)用時,你會如何去管理應(yīng)用的狀態(tài)數(shù)據(jù)呢?
我們認為應(yīng)用的數(shù)據(jù)大體上可以分為四類:
事件:瞬間產(chǎn)生的數(shù)據(jù),數(shù)據(jù)被消費后立即銷毀,不存儲。
異步:異步獲取的數(shù)據(jù);類似于事件,是瞬間數(shù)據(jù),不存儲。
狀態(tài):隨著時間空間變化的數(shù)據(jù),始終會存儲一個當(dāng)前值/最新值。
常量:固定不變的數(shù)據(jù)。
RxJS天生就適合編寫異步和基于事件的程序,那么狀態(tài)數(shù)據(jù)用什么去管理呢?還是用RxJS嗎? 合不合適呢?
我們?nèi)フ{(diào)研和學(xué)習(xí)了前端社區(qū)已有的優(yōu)秀的狀態(tài)管理解決方案,也從一些大牛分享的關(guān)于用RxJS設(shè)計數(shù)據(jù)層的構(gòu)想和實踐中得到了啟發(fā):
使用RxJS完全可以實現(xiàn)諸如Redux,Mobx等管理狀態(tài)數(shù)據(jù)的功能。
應(yīng)用的數(shù)據(jù)不是只有狀態(tài)的,還有事件、異步、常量等等。如果整個應(yīng)用都由observable來表達,則可以借助RxJS基于序列且可響應(yīng)的的特性,以流的方式自由地拼接和組合各種類型的數(shù)據(jù),能夠更優(yōu)雅更高效地抽象出可復(fù)用可擴展的業(yè)務(wù)模型。
出于以上兩點原因,最終決定基于RxJS來設(shè)計一套管理應(yīng)用的狀態(tài)的解決方案。
原理介紹對于狀態(tài)的定義,通常認為狀態(tài)需要滿足以下3個條件:
是一個具有多個值的集合。
能夠通過event或者action對值進行轉(zhuǎn)換,從而得到新的值。
有“當(dāng)前值”的概念,對外一般只暴露當(dāng)前值,即最新值。
那么,RxJS適合用來管理狀態(tài)數(shù)據(jù)嗎?答案是肯定的!
首先,因為Observable本身就是多個值的推送集合,所以第一個條件是滿足的!
其次,我們可以實現(xiàn)一個使用dispatch action模式來推送數(shù)據(jù)的observable來滿足第二個條件!
眾所周知,RxJS中的observable可以分為兩種類型:
cold observable: 推送值的生產(chǎn)者(producer)來自observable內(nèi)部。
將會推送幾個值以及推送什么樣的值已在observable創(chuàng)建時被定義下來,不可改變。
producer與觀察者(observer) 是一對一的關(guān)系,即是單播的。
每當(dāng)有observer訂閱時,producer都會把預(yù)先定義好的若干個值依次推送給observer。
hot observable: 推送值的producer來自observable外部。
將會推送幾個值、推送什么樣的值以及何時推送在創(chuàng)建時都是未知的。
producer與observer是一對多的關(guān)系,即是多播的。
每當(dāng)有observer訂閱時,會將observer注冊到觀察者列表中,類似于其他庫或語言中的addListener的工作方式。
當(dāng)外部的producer被觸發(fā)或執(zhí)行時,會將值同時推送給所有的observer;也就是說,所有的observer共享了hot observable推送的值。
RxJS提供的BehaviorSubject就是一種特殊的hot observable,它向外暴露了推送數(shù)據(jù)的接口next函數(shù);并且有“當(dāng)前值”的概念,它保存了發(fā)送給observer的最新值,當(dāng)有新的觀察者訂閱時,會立即從BehaviorSubject那接收到“當(dāng)前值”。
那么這說明使用BehaviorSubject來更新狀態(tài)并保存狀態(tài)的當(dāng)前值是可行的,第三個條件也滿足了。
簡單實現(xiàn)請看以下的代碼:
import { BehaviorSubject } from "rxjs"; // 數(shù)據(jù)推送的生產(chǎn)者 class StateMachine { constructor(subject, value) { this.subject = subject; this.value = value; } producer(action) { let oldValue = this.value; let newValue; switch (action.type) { case "plus": newValue = ++oldValue; this.value = newValue; this.subject.next(newValue); break; case "toDouble": newValue = oldValue * 2; this.value = newValue; this.subject.next(newValue); break; } } } const value = 1; // 狀態(tài)的初始值 const count$ = new BehaviorSubject(value); const stateMachine = new StateMachine(count$, value); // 派遣action function dispatch(action) { stateMachine.producer(action); } count$.subscribe(val => { console.log(val); }); setTimeout(() => { dispatch({ type: "plus" }); }, 1000); setTimeout(() => { dispatch({ type: "toDouble" }); }, 2000);
執(zhí)行代碼控制臺會打印出三個值:
Console 1 2 4
上面的代碼簡單實現(xiàn)了一個簡單管理狀態(tài)的例子:
狀態(tài)的初始值: 1
執(zhí)行plus之后的狀態(tài)值: 2
執(zhí)行toDouble之后的狀態(tài)值: 4
實現(xiàn)方法挺簡單的,就是使用BehaviorSubject來表達狀態(tài)的當(dāng)前值:
第一步,通過調(diào)用dispatch函數(shù)使producer函數(shù)執(zhí)行
第二部,producer函數(shù)在內(nèi)部調(diào)用了BehaviorSubject的next函數(shù),推送了新數(shù)據(jù),BehaviorSubject的當(dāng)前值更新了,也就是狀態(tài)更新了。
不過寫起來略微繁瑣,我們對其進行了封裝,優(yōu)化后寫法見下文。
使用操作符來創(chuàng)建狀態(tài)數(shù)據(jù)我們自定義了一個操作符state用來創(chuàng)建一個能夠通過dispatch action模式推送新數(shù)據(jù)的BehaviorSubject,我們稱她為stateObservable。
const count$ = state({ // 狀態(tài)的唯一標(biāo)識名稱 name: "count", // 狀態(tài)的默認值 defaultValue: 1, // 數(shù)據(jù)推送的生產(chǎn)者函數(shù) producer(next, value, action) { switch (action.type) { case "plus": next(value + 1); break; case "toDouble": next(value * 2); break; } } });更新狀態(tài)
在你想要的任意位置使用函數(shù)dispatch派遣action即可更新狀態(tài)!
dispatch("count", { type: "plus" })異步數(shù)據(jù)
RxJS的一大優(yōu)勢就在于能夠統(tǒng)一同步和異步,使用observable處理數(shù)據(jù)你不需要關(guān)注同步還是異步。
下面的例子我們使用操作符from將promise轉(zhuǎn)換為observable。
指定observable作為狀態(tài)的初始值(首次推送數(shù)據(jù))const todos$ = state({ name: "todos", // `observable`推送的數(shù)據(jù)將作為狀態(tài)的初始值 initial: from(getAsyncData()) //... });producer推送observable
const todos$ = state({ name: "todos", defaultValue: [] // 數(shù)據(jù)推送的生產(chǎn)者函數(shù) producer(next, value, action) { switch (action.type) { case "getAsyncData": next( from(getAsyncData()) ); break; } } });
執(zhí)行getAsyncData之后,from(getAsyncData())的推送數(shù)據(jù)將成為狀態(tài)的最新值。
衍生狀態(tài)由于狀態(tài)todos$是一個observable,所以可以很自然地使用RxJS操作符轉(zhuǎn)換得到另一個新的observable。并且這個observable的推送來自todos$;也就是說只要todos$推送新數(shù)據(jù),它也會推送;效果類似于Vue的計算屬性。
// 未完成任務(wù)數(shù)量 const undoneCount$ = todos$.pipe( map(todos => { let _conut = 0; todos.forEach(item => { if (!item.check) ++_conut; }); return _conut; }) );React視圖渲染
我們可能會在組件的生命周期內(nèi)訂閱observable得到數(shù)據(jù)渲染視圖。
class Todos extends React.Component { componentWillMount() { todos$.subscribe(data => { this.setState({ todos: data }); }); } }
我們可以再優(yōu)化下,利用高階組件封裝一個裝飾器函數(shù)@subscription,顧名思義,就是為React組件訂閱observable以響應(yīng)推送數(shù)據(jù)的變化;它會將observable推送的數(shù)據(jù)轉(zhuǎn)換為React組件的props。
@subscription({ todos: todos$ }) class TodoList extends React.Component { render() { return (總結(jié)); } }任務(wù)列表
{this.props.todos.map((item, n) => { return; })}
使用RxJS越久,越令人受益匪淺。
因為它基于observable序列提供了較高層次的抽象,并且是觀察者模式,可以盡可能地減少各組件各模塊之間的耦合度,大大減輕了定位BUG和重構(gòu)的負擔(dān)。
因為是基于observable序列來編寫代碼的,所以遇到復(fù)雜的業(yè)務(wù)場景,總能按照一定的順序使用observable描述出來,代碼的可讀性很強。并且當(dāng)需求變動時,我可能只需要調(diào)整下observable的順序,或者加個操作符就行了。再也不必因為一個復(fù)雜的業(yè)務(wù)流程改動了,需要去改好幾個地方的代碼(而且還容易改出BUG,笑~)。
所以,以上基于RxJS的狀態(tài)管理方案,對我們來說是一個必需品,因為我們項目中大量使用了RxJS,如果狀態(tài)數(shù)據(jù)也是observable,對我們抽象可復(fù)用可擴展的業(yè)務(wù)模型是一個非常大的助力。當(dāng)然了,如果你的項目中沒有使用RxJS,也許Redux和Mobx是更合適的選擇。
這套基于RxJS的狀態(tài)管理方案,我們已經(jīng)用于開發(fā)公司的商用項目,反饋還不錯。所以我們決定把這套方案整理成一個js lib,取名為:Floway,并在github上開源:
github源碼:https://github.com/shayeLee/floway
使用文檔:https://shayelee.github.io/floway
歡迎大家star,更歡迎大家來共同交流和分享RxJS的使用心得!
參考文章:
復(fù)雜單頁應(yīng)用的數(shù)據(jù)層設(shè)計
DaoCloud 基于 RxJS 的前端數(shù)據(jù)層實踐
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/109340.html
摘要:前戲補上參會的完整記錄,這個問題從一開始我就是準備自問自答的,希望可以通過這種形式把大會的干貨分享給更多人。 showImg(http://7xqy7v.com1.z0.glb.clouddn.com/colorful/blog/feday2.png); 前戲 2016/3/21 補上參會的完整記錄,這個問題從一開始我就是準備自問自答的,希望可以通過這種形式把大會的干貨分享給更多人。 ...
摘要:要求通過要求數(shù)據(jù)變更函數(shù)使用裝飾或放在函數(shù)中,目的就是讓狀態(tài)的變更根據(jù)可預(yù)測性單向數(shù)據(jù)流。同一份數(shù)據(jù)需要響應(yīng)到多個視圖,且被多個視圖進行變更需要維護全局狀態(tài),并在他們變動時響應(yīng)到視圖數(shù)據(jù)流變得復(fù)雜,組件本身已經(jīng)無法駕馭。今天是 520,這是本系列最后一篇文章,主要涵蓋 React 狀態(tài)管理的相關(guān)方案。 前幾篇文章在掘金首發(fā)基本石沉大海, 沒什么閱讀量. 可能是文章篇幅太長了?掘金值太低了? ...
摘要:技術(shù)積累經(jīng)過社區(qū)的努力學(xué)習(xí)資料還是很多的,官方中文文檔就已經(jīng)很不錯,不過我們先從天精通初步感受一下然后配合一些中文文檔來補充知識點,最后再根據(jù)官方文檔來校驗整個知識體系。資料學(xué)習(xí)操作符的時候可以對照彈珠圖的交互彈珠圖的中文版中文文檔 前言 最近準備畢設(shè),技術(shù)選型的時候因為功能的一些需求準備將RxJs融入到項目中,考慮RxJs的時候因為之前的技術(shù)棧還猶豫了一下,查了一些資料以及粗略瀏覽了...
摘要:實現(xiàn)不定期更新技巧前端掘金技巧,偶爾更新。統(tǒng)一播放效果實現(xiàn)打字效果動畫前端掘金前端開源項目周報前端掘金由出品的前端開源項目周報第四期來啦。 Web 推送技術(shù) - 掘金騰訊云技術(shù)社區(qū)-掘金主頁持續(xù)為大家呈現(xiàn)云計算技術(shù)文章,歡迎大家關(guān)注! 作者:villainthr 摘自 前端小吉米 伴隨著今年 Google I/O 大會的召開,一個很火的概念--Progressive Web Apps ...
閱讀 3144·2023-04-25 20:43
閱讀 1755·2021-09-30 09:54
閱讀 1620·2021-09-24 09:47
閱讀 2922·2021-09-06 15:02
閱讀 3542·2021-02-22 17:09
閱讀 1274·2019-08-30 15:53
閱讀 1473·2019-08-29 17:04
閱讀 1993·2019-08-28 18:22