摘要:隨著前端應用的復雜度越來越高,如何管理應用的數據已經是一個不可回避的問題。應用的數據不是只有狀態的,還有事件異步常量等等。出于以上兩點原因,最終決定基于來設計一套管理應用的狀態的解決方案。
隨著前端應用的復雜度越來越高,如何管理應用的數據已經是一個不可回避的問題。當你面對的是業務場景復雜、需求變動頻繁、各種應用數據互相關聯依賴的大型前端應用時,你會如何去管理應用的狀態數據呢?
我們認為應用的數據大體上可以分為四類:
事件:瞬間產生的數據,數據被消費后立即銷毀,不存儲。
異步:異步獲取的數據;類似于事件,是瞬間數據,不存儲。
狀態:隨著時間空間變化的數據,始終會存儲一個當前值/最新值。
常量:固定不變的數據。
RxJS天生就適合編寫異步和基于事件的程序,那么狀態數據用什么去管理呢?還是用RxJS嗎? 合不合適呢?
我們去調研和學習了前端社區已有的優秀的狀態管理解決方案,也從一些大牛分享的關于用RxJS設計數據層的構想和實踐中得到了啟發:
使用RxJS完全可以實現諸如Redux,Mobx等管理狀態數據的功能。
應用的數據不是只有狀態的,還有事件、異步、常量等等。如果整個應用都由observable來表達,則可以借助RxJS基于序列且可響應的的特性,以流的方式自由地拼接和組合各種類型的數據,能夠更優雅更高效地抽象出可復用可擴展的業務模型。
出于以上兩點原因,最終決定基于RxJS來設計一套管理應用的狀態的解決方案。
原理介紹對于狀態的定義,通常認為狀態需要滿足以下3個條件:
是一個具有多個值的集合。
能夠通過event或者action對值進行轉換,從而得到新的值。
有“當前值”的概念,對外一般只暴露當前值,即最新值。
那么,RxJS適合用來管理狀態數據嗎?答案是肯定的!
首先,因為Observable本身就是多個值的推送集合,所以第一個條件是滿足的!
其次,我們可以實現一個使用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
上面的代碼簡單實現了一個簡單管理狀態的例子:
狀態的初始值: 1
執行plus之后的狀態值: 2
執行toDouble之后的狀態值: 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; }) );React視圖渲染
我們可能會在組件的生命周期內訂閱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 (總結); } }任務列表
{this.props.todos.map((item, n) => { return; })}
使用RxJS越久,越令人受益匪淺。
因為它基于observable序列提供了較高層次的抽象,并且是觀察者模式,可以盡可能地減少各組件各模塊之間的耦合度,大大減輕了定位BUG和重構的負擔。
因為是基于observable序列來編寫代碼的,所以遇到復雜的業務場景,總能按照一定的順序使用observable描述出來,代碼的可讀性很強。并且當需求變動時,我可能只需要調整下observable的順序,或者加個操作符就行了。再也不必因為一個復雜的業務流程改動了,需要去改好幾個地方的代碼(而且還容易改出BUG,笑~)。
所以,以上基于RxJS的狀態管理方案,對我們來說是一個必需品,因為我們項目中大量使用了RxJS,如果狀態數據也是observable,對我們抽象可復用可擴展的業務模型是一個非常大的助力。當然了,如果你的項目中沒有使用RxJS,也許Redux和Mobx是更合適的選擇。
這套基于RxJS的狀態管理方案,我們已經用于開發公司的商用項目,反饋還不錯。所以我們決定把這套方案整理成一個js lib,取名為:Floway,并在github上開源:
github源碼:https://github.com/shayeLee/floway
使用文檔:https://shayelee.github.io/floway
歡迎大家star,更歡迎大家來共同交流和分享RxJS的使用心得!
參考文章:
復雜單頁應用的數據層設計
DaoCloud 基于 RxJS 的前端數據層實踐
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/109340.html
摘要:前戲補上參會的完整記錄,這個問題從一開始我就是準備自問自答的,希望可以通過這種形式把大會的干貨分享給更多人。 showImg(http://7xqy7v.com1.z0.glb.clouddn.com/colorful/blog/feday2.png); 前戲 2016/3/21 補上參會的完整記錄,這個問題從一開始我就是準備自問自答的,希望可以通過這種形式把大會的干貨分享給更多人。 ...
摘要:要求通過要求數據變更函數使用裝飾或放在函數中,目的就是讓狀態的變更根據可預測性單向數據流。同一份數據需要響應到多個視圖,且被多個視圖進行變更需要維護全局狀態,并在他們變動時響應到視圖數據流變得復雜,組件本身已經無法駕馭。今天是 520,這是本系列最后一篇文章,主要涵蓋 React 狀態管理的相關方案。 前幾篇文章在掘金首發基本石沉大海, 沒什么閱讀量. 可能是文章篇幅太長了?掘金值太低了? ...
摘要:技術積累經過社區的努力學習資料還是很多的,官方中文文檔就已經很不錯,不過我們先從天精通初步感受一下然后配合一些中文文檔來補充知識點,最后再根據官方文檔來校驗整個知識體系。資料學習操作符的時候可以對照彈珠圖的交互彈珠圖的中文版中文文檔 前言 最近準備畢設,技術選型的時候因為功能的一些需求準備將RxJs融入到項目中,考慮RxJs的時候因為之前的技術棧還猶豫了一下,查了一些資料以及粗略瀏覽了...
摘要:實現不定期更新技巧前端掘金技巧,偶爾更新。統一播放效果實現打字效果動畫前端掘金前端開源項目周報前端掘金由出品的前端開源項目周報第四期來啦。 Web 推送技術 - 掘金騰訊云技術社區-掘金主頁持續為大家呈現云計算技術文章,歡迎大家關注! 作者:villainthr 摘自 前端小吉米 伴隨著今年 Google I/O 大會的召開,一個很火的概念--Progressive Web Apps ...
閱讀 3076·2023-04-25 20:43
閱讀 1719·2021-09-30 09:54
閱讀 1590·2021-09-24 09:47
閱讀 2874·2021-09-06 15:02
閱讀 3510·2021-02-22 17:09
閱讀 1233·2019-08-30 15:53
閱讀 1441·2019-08-29 17:04
閱讀 1956·2019-08-28 18:22