隨着前端應用的複雜度愈來愈高,如何管理應用的數據已是一個不可迴避的問題。當你面對的是 業務場景複雜、需求變更頻繁、各類應用數據互相關聯依賴的大型前端應用時,你會如何去管理應用的狀態數據呢?
咱們認爲應用的數據大致上能夠分爲四類:javascript
RxJS
天生就適合編寫異步和基於事件的程序,那麼狀態數據用什麼去管理呢?仍是用RxJS
嗎? 合不合適呢?前端
咱們去調研和學習了前端社區已有的優秀的狀態管理解決方案,也從一些大牛分享的關於用RxJS
設計數據層的構想和實踐中獲得了啓發:java
RxJS
徹底能夠實現諸如Redux
,Mobx
等管理狀態數據的功能。observable
來表達,則能夠藉助RxJS
基於序列且可響應的的特性,以流的方式自由地拼接和組合各類類型的數據,可以更優雅更高效地抽象出可複用可擴展的業務模型。出於以上兩點緣由,最終決定基於RxJS
來設計一套管理應用的狀態的解決方案。react
對於狀態的定義,一般認爲狀態須要知足如下3個條件:git
event
或者action
對值進行轉換,從而獲得新的值。那麼,RxJS
適合用來管理狀態數據嗎?答案是確定的!github
首先,由於Observable
自己就是多個值的推送集合,因此第一個條件是知足的!promise
其次,咱們能夠實現一個使用dispatch action
模式來推送數據的observable
來知足第二個條件!異步
衆所周知,RxJS
中的observable
能夠分爲兩種類型:函數
cold observable
: 推送值的生產者(producer
)來自observable
內部。學習
observable
建立時被定義下來,不可改變。producer
與觀察者(observer
) 是一對一的關係,便是單播的。observer
訂閱時,producer
都會把預先定義好的若干個值依次推送給observer
。hot observable
: 推送值的producer
來自observable
外部。
producer
與observer
是一對多的關係,便是多播的。observer
訂閱時,會將observer
註冊到觀察者列表中,相似於其餘庫或語言中的addListener
的工做方式。producer
被觸發或執行時,會將值同時推送給全部的observer
;也就是說,全部的observer
共享了hot observable
推送的值。RxJS
提供的BehaviorSubject
就是一種特殊的hot observable
,它向外暴露了推送數據的接口next
函數;而且有「當前值」的概念,它保存了發送給observer
的最新值,當有新的觀察者訂閱時,會當即從BehaviorSubject
那接收到「當前值」。
那麼這說明使用BehaviorSubject
來更新狀態並保存狀態的當前值是可行的,第三個條件也知足了。
請看如下的代碼:
import { BehaviorSubject } from 'rxjs'; // 數據推送的生產者 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; // 狀態的初始值 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);
執行代碼控制檯會打印出三個值:
Console 1 2 4
上面的代碼簡單實現了一個簡單管理狀態的例子:
plus
以後的狀態值: 2toDouble
以後的狀態值: 4實現方法挺簡單的,就是使用BehaviorSubject
來表達狀態的當前值:
dispatch
函數使producer
函數執行producer
函數在內部調用了BehaviorSubject
的next
函數,推送了新數據,BehaviorSubject
的當前值更新了,也就是狀態更新了。不過寫起來略微繁瑣,咱們對其進行了封裝,優化後寫法見下文。
咱們自定義了一個操做符state
用來建立一個可以經過dispatch action
模式推送新數據的BehaviorSubject
,咱們稱她爲stateObservable
。
const count$ = state({ // 狀態的惟一標識名稱 name: "count", // 狀態的默認值 defaultValue: 1, // 數據推送的生產者函數 producer(next, value, action) { switch (action.type) { case "plus": next(value + 1); break; case "toDouble": next(value * 2); break; } } });
在你想要的任意位置使用函數dispatch
派遣action
便可更新狀態!
dispatch("count", { type: "plus" })
RxJS
的一大優點就在於可以統一同步和異步,使用observable
處理數據你不須要關注同步仍是異步。
下面的例子咱們使用操做符from
將promise
轉換爲observable
。
observable
做爲狀態的初始值(首次推送數據)const todos$ = state({ name: "todos", // `observable`推送的數據將做爲狀態的初始值 initial: from(getAsyncData()) //... });
producer
推送observable
const todos$ = state({ name: "todos", defaultValue: [] // 數據推送的生產者函數 producer(next, value, action) { switch (action.type) { case "getAsyncData": next( from(getAsyncData()) ); break; } } });
執行getAsyncData
以後,from(getAsyncData())
的推送數據將成爲狀態的最新值。
因爲狀態todos$
是一個observable
,因此能夠很天然地使用RxJS
操做符轉換獲得另外一個新的observable
。而且這個observable
的推送來自todos$
;也就是說只要todos$
推送新數據,它也會推送;效果相似於Vue
的計算屬性。
// 未完成任務數量 const undoneCount$ = todos$.pipe( map(todos => { let _conut = 0; todos.forEach(item => { if (!item.check) ++_conut; }); return _conut; }) );
咱們可能會在組件的生命週期內訂閱observable
獲得數據渲染視圖。
class Todos extends React.Component { componentWillMount() { todos$.subscribe(data => { this.setState({ todos: data }); }); } }
咱們能夠再優化下,利用高階組件封裝一個裝飾器函數@subscription
,顧名思義,就是爲React組件訂閱observable
以響應推送數據的變化;它會將observable
推送的數據轉換爲React組件的props
。
@subscription({ todos: todos$ }) class TodoList extends React.Component { render() { return ( <div className="todolist"> <h1 className="header">任務列表</h1> {this.props.todos.map((item, n) => { return <TodoItem item={item} key={item.desc} />; })} </div> ); } }
使用RxJS
越久,越使人受益不淺。
observable
序列提供了較高層次的抽象,而且是觀察者模式,能夠儘量地減小各組件各模塊之間的耦合度,大大減輕了定位BUG和重構的負擔。observable
序列來編寫代碼的,因此遇到複雜的業務場景,總能按照必定的順序使用observable
描述出來,代碼的可讀性很強。而且當需求變更時,我可能只須要調整下observable
的順序,或者加個操做符就好了。不再必由於一個複雜的業務流程改動了,須要去改好幾個地方的代碼(並且還容易改出BUG,笑~)。因此,以上基於RxJS
的狀態管理方案,對咱們來講是一個必需品,由於咱們項目中大量使用了RxJS
,若是狀態數據也是observable
,對咱們抽象可複用可擴展的業務模型是一個很是大的助力。固然了,若是你的項目中沒有使用RxJS
,也許Redux
和Mobx
是更合適的選擇。
這套基於RxJS
的狀態管理方案,咱們已經用於開發公司的商用項目,反饋還不錯。因此咱們決定把這套方案整理成一個js lib
,取名爲:Floway
,並在github
上開源:
歡迎你們star
,更歡迎你們來共同交流和分享RxJS
的使用心得!
參考文章: