當(dāng)下前端流行的框架,都是用狀態(tài)來(lái)描述界面(state => view),可以說(shuō)前端開發(fā)實(shí)際上就是在維護(hù)各種狀態(tài)(state),這已經(jīng)成為目前前端開發(fā)的共識(shí)。
復(fù)制代碼
View = ViewModel(Model);
理想情況下,ViewModel 是純函數(shù),給定相同的 Model,產(chǎn)出相同的 View。
state => view 很好理解,但如何在 view 中合理地修改 state 也是一個(gè)問(wèn)題。
為什么需要狀態(tài)管理
舉個(gè)例子
圖書館的管理,原來(lái)是開放式的,所有人可以隨意進(jìn)出書庫(kù)借書還書,如果人數(shù)不多,這種方式可以減少流程,增加效率,一旦人數(shù)變多就勢(shì)必造成混亂。
Flux 就像是給這個(gè)圖書館加上了一個(gè)管理員,所有借書還書的行為都需要委托管理員去做,管理員會(huì)規(guī)范對(duì)書庫(kù)的操作行為,也會(huì)記錄每個(gè)人的操作,減少混亂的現(xiàn)象。
一個(gè)比喻
我們寄一件東西的過(guò)程
沒(méi)有快遞時(shí):
- 打包準(zhǔn)備好要送出去的東西
- 直接到朋友家,把東西送給朋友
- 很直接很方便,很費(fèi)時(shí)間
有了快遞公司:
- 打包準(zhǔn)備好要送出去的東西
- 到快遞公司,填寫物品,收件人等基本信息
- 快遞公司替你送物品到你的朋友家,我們的工作結(jié)束了
多了快遞公司,讓快遞公司給我們送快遞。
當(dāng)我們只寄送物品給一個(gè)朋友,次數(shù)較少,物品又較少的時(shí)候,我們直接去朋友家就挺好的。但當(dāng)我們要頻繁寄送給很多朋友很多商品的時(shí)候,問(wèn)題就復(fù)雜了。
軟件工程的本質(zhì)即是管理復(fù)雜度。使用狀態(tài)管理類框架會(huì)有一定的學(xué)習(xí)成本而且通常會(huì)把簡(jiǎn)單的事情做復(fù)雜,但如果我們想做復(fù)雜一點(diǎn)的事情(同時(shí)寄很多物品到多個(gè)不同地址),對(duì)我們來(lái)說(shuō),快遞會(huì)讓復(fù)雜的事情變的簡(jiǎn)單。
這同時(shí)也解釋了,是否需要添加狀態(tài)管理框架,我們可以根據(jù)自己的業(yè)務(wù)實(shí)際情況和技術(shù)團(tuán)隊(duì)的偏好而添加,有些情況下,創(chuàng)建一個(gè)全局對(duì)象就能解決很多問(wèn)題。
核心思想
Flux 的核心思想:數(shù)據(jù)單向流動(dòng)。
- 不同組件的 state,存放在一個(gè)外部的、公共的 Store 上面。
- 組件訂閱 Store 的不同部分。
- 組件發(fā)送(dispatch)動(dòng)作(action),引發(fā) Store 的更新。
Redux 的核心概念
- 所有的狀態(tài)存放在 Store。組件每次重新渲染,都必須由狀態(tài)變化引起。
- 用戶在 UI 上發(fā)出 action。
- reducer 函數(shù)接收 action,然后根據(jù)當(dāng)前的 state,計(jì)算出新的 state。
Redux store 是單一數(shù)據(jù)源。Redux 沒(méi)有 dispatcher 的概念,轉(zhuǎn)而使用純函數(shù)(pure function)代替。
Redux store 是不可變的(Immutable)。
MobX
- Observable:它的 state 是可被觀察的,無(wú)論是基本數(shù)據(jù)類型還是引用數(shù)據(jù)類型,都可以使用 MobX 的 (@)observable 來(lái)轉(zhuǎn)變?yōu)?observable value。
- Reactions:它包含不同的概念,基于被觀察數(shù)據(jù)的更新導(dǎo)致某個(gè)計(jì)算值(computed values),或者是發(fā)送網(wǎng)絡(luò)請(qǐng)求以及更新視圖等,都屬于響應(yīng)的范疇,這也是響應(yīng)式編程(Reactive Programming)在 JavaScript 中的一個(gè)應(yīng)用。
- Actions:它相當(dāng)于所有響應(yīng)的源頭,例如用戶在視圖上的操作,或是某個(gè)網(wǎng)絡(luò)請(qǐng)求的響應(yīng)導(dǎo)致的被觀察數(shù)據(jù)的變更。
和 Redux 對(duì)單向數(shù)據(jù)流的嚴(yán)格規(guī)范不同,Mobx 只專注于從 store 到 view 的過(guò)程。在 Redux 中,數(shù)據(jù)的變更需要監(jiān)聽,而 Mobx 的數(shù)據(jù)依賴是基于運(yùn)行時(shí)的,這點(diǎn)和 Vuex 更為接近。
Vuex 的狀態(tài)管理模式
- state,驅(qū)動(dòng)應(yīng)用的數(shù)據(jù)源;
- view,以聲明方式將 state 映射到視圖;
- actions,響應(yīng)在 view 上的用戶輸入導(dǎo)致的狀態(tài)變化。
Flux
Facebook 提出了 Flux 架構(gòu)思想,規(guī)范了數(shù)據(jù)在應(yīng)用中的流動(dòng)方式。其基本架構(gòu)如下入所示,其核心理念是單向數(shù)據(jù)流,它完善了 React 對(duì)應(yīng)用狀態(tài)的管理。
?
上圖描述了頁(yè)面的啟動(dòng)和運(yùn)行原理:
1.通過(guò) dispatcher 派發(fā) action,并利用 store 中的 action 處理邏輯更新狀態(tài)和 view
2.而 view 也可以觸發(fā)新的 action,從而進(jìn)入新的步驟 1
其中的 action 是用于描述動(dòng)作的簡(jiǎn)單對(duì)象,通常通過(guò)用戶對(duì) view 的操作產(chǎn)生,包括動(dòng)作類型和動(dòng)作所攜帶的所需參數(shù),比如描述刪除列表項(xiàng)的 action:
復(fù)制代碼
{ type: types.DELETE_ITEM, id: id };
而 dispatcher 用于對(duì) action 進(jìn)行分發(fā),分發(fā)的目標(biāo)就是注冊(cè)在 store 里的事件處理函數(shù):
復(fù)制代碼
dispatcher.register(function(action) { switch (action.type) { case "DELETE_ITEM": sotre.deleteItem(action.id); //更新狀態(tài) store.emitItemDeleted(); //通知視圖更新 break; default: // no op } });
store 包含了應(yīng)用的所有狀態(tài)和邏輯,它有點(diǎn)像傳統(tǒng)的 MVC 模型中的 model 層,但又與之有明顯的區(qū)別,store 包括的是一個(gè)應(yīng)用特定功能的全部狀態(tài)和邏輯,它代表了應(yīng)用的整個(gè)邏輯層;而不是像 Model 一樣包含的是數(shù)據(jù)庫(kù)中的一些記錄和與之對(duì)應(yīng)的邏輯。
參考鏈接:flux
Redux
原生 Redux API 最簡(jiǎn)單的用例
復(fù)制代碼
function counter(state, action) { if (typeof state === "undefined") { return 0; } switch (action.type) { case "INCREMENT": return state + 1; case "DECREMENT": return state - 1; default: return state; } } var store = Redux.createStore(counter); // var valueEl = document.getElementById("value"); function render() { valueEl.innerHTML = store.getState().toString(); } render(); store.subscribe(render); document.getElementById("increment").addEventListener("click", function() { store.dispatch({ type: "INCREMENT" }); }); document.getElementById("decrement").addEventListener("click", function() { store.dispatch({ type: "DECREMENT" }); }); document.getElementById("incrementIfOdd").addEventListener("click", function() { if (store.getState() % 2 !== 0) { store.dispatch({ type: "INCREMENT" }); } }); document.getElementById("incrementAsync").addEventListener("click", function() { setTimeout(function() { store.dispatch({ type: "INCREMENT" }); }, 1000); });
應(yīng)用中所有的 state 都以一個(gè)對(duì)象樹的形式儲(chǔ)存在一個(gè)單一的 store 中。 改變 state 的唯一辦法是觸發(fā) action,一個(gè)描述發(fā)生什么的對(duì)象。 為了描述 action 如何改變 state 樹,你需要編寫 reducers。
Redux 三大原則
-
單一數(shù)據(jù)源
整個(gè)應(yīng)用的
state
被儲(chǔ)存在一棵 object tree 中,并且這個(gè) object tree 只存在于唯一一個(gè)store
中。 -
state 是只讀的
唯一改變 state 的方法就是觸發(fā) action,action 是一個(gè)用于描述已發(fā)生事件的普通對(duì)象。
-
使用純函數(shù)來(lái)執(zhí)行修改
為了描述 action 如何改變 state tree ,你需要編寫
reducers
。 改變state
的惟一方法是dispatch action
。你也可以subscribe
監(jiān)聽state
的變化,然后更新 UI。
嚴(yán)格的單向數(shù)據(jù)流是 Redux 架構(gòu)的設(shè)計(jì)核心。
Redux API
Redux 的 API 非常少。
Redux 定義了一系列的約定(contract)來(lái)讓你來(lái)實(shí)現(xiàn)(例如 reducers),同時(shí)提供少量輔助函數(shù)來(lái)把這些約定整合到一起。
Redux 只關(guān)心如何管理 state。在實(shí)際的項(xiàng)目中,你還需要使用 UI 綁定庫(kù)如 react-redux。
- createStore(reducer, [preloadedState], [enhancer])
- combineReducers(reducers)
- bindActionCreators(actionCreators, dispatch)
- applyMiddleware(...middlewares)
- compose(...functions)
immutable
在寫 redux 的 action 的時(shí)候,總是需要用到擴(kuò)展語(yǔ)句或者 Object.assign()
的方式來(lái)得到一個(gè)新的 state,這一點(diǎn)對(duì)于 JavaScript 而言是對(duì)象的淺拷貝,它對(duì)內(nèi)存的開銷肯定是大于 mobX 中那樣直接操作對(duì)象屬性的方式大得多。
參考鏈接:redux-immutable seamless-immutable reselect 為什么使用 Redux 管理狀態(tài)是可預(yù)測(cè)的
redux-saga
redux 是 react 技術(shù)棧中的狀態(tài)控制流框架,使用了標(biāo)準(zhǔn)的函數(shù)式思想,期望(強(qiáng)制)所有狀態(tài)管理都是純函數(shù)。這也意味著各狀態(tài)之間都是獨(dú)立的。但是有一類狀態(tài) redux 直接甩給了的第三方模塊,副作用模塊 redux-saga
也就成了任勞任怨的典型代表。副作用正是因?yàn)椴淮_定性和可變性而得名,而其給出的狀態(tài)又是相互影響,如何解耦使得原本復(fù)雜的非線性呈現(xiàn)為線性邏輯,正是有限狀態(tài)機(jī)的用武之處。
DvaJS
dva 首先是一個(gè)基于 redux
和 redux-saga
的數(shù)據(jù)流方案,然后為了簡(jiǎn)化開發(fā)體驗(yàn),dva 還額外內(nèi)置了 react-router
和 fetch
,所以也可以理解為一個(gè)輕量級(jí)的應(yīng)用框架。
在 redux 的生態(tài)圈內(nèi),每個(gè)環(huán)節(jié)有多種方案,比如 Data 可以是 immutable 或者 plain object,在你選了 immutable 之后,用 immutable.js 還是 seamless-immutable,以及是否用 redux-immutable 來(lái)輔助數(shù)據(jù)修改,都需要選擇。
參考鏈接:Redux 中文文檔 immutable-js immer dvajs React + Redux 最佳實(shí)踐
MobX
MobX 是一個(gè)用法簡(jiǎn)單優(yōu)雅、同時(shí)具有可擴(kuò)展性的狀態(tài)管理庫(kù)。
一個(gè)簡(jiǎn)單的例子
復(fù)制代碼
import { observable, autorun } from "mobx"; const appState = observable({ counter: 0, add(value) { this.counter += value; } }); autorun(() => console.log(appState.counter)); setInterval(() => appState.add(1), 1000);
在 mobx 中我們可以直接修改狀態(tài)
復(fù)制代碼
import { observable } from "mobx"; const appState = observable({ counter: 0 }); appState.counter += 1;
可以通過(guò)引入 Strict 模式來(lái)避免這種不良好的實(shí)踐:
復(fù)制代碼
import { useStrict } from "mobx"; useStrict(true);
MobX 脫胎于響應(yīng)式編程(Reactive Programming),其核心思想為 Anything that can be derived from the application state, should be derived. Automatically,即避免任何的重復(fù)狀態(tài)。
MobX 中核心的概念即是 Observable,相信接觸過(guò)響應(yīng)式編程的肯定非常熟悉,從后端的典型代表 RxJava 到 Android/iOS 開發(fā)中的各種響應(yīng)式框架都各領(lǐng)風(fēng)騷。
與 Redux 狀態(tài)管理上的異同
Redux / MobX 均為客戶端開源狀態(tài)管理庫(kù),用狀態(tài)來(lái)描述 UI 界面,它們與 React 都不具有強(qiáng)綁定關(guān)系,你也可以配合別的框架來(lái)使用它們。 當(dāng)然,與 React 是再合適不過(guò)的了,React 作為 View 層的框架,通過(guò) Virtual DOM 機(jī)制來(lái)優(yōu)化 UI 渲染,Redux / MobX 則提供了將相應(yīng)狀態(tài)同步到 React 的機(jī)制。
Redux 與 MobX 的不同主要集中于以下幾點(diǎn):
- Redux 是單一數(shù)據(jù)源,而 MobX 往往是多個(gè) store。MobX 可以根據(jù)應(yīng)用的 UI、數(shù)據(jù)或業(yè)務(wù)邏輯來(lái)組織 store,具體如何進(jìn)行需要你自己進(jìn)行權(quán)衡。
- Redux store 使用普通的 JavaScript 對(duì)象結(jié)構(gòu),MobX 將常規(guī) JavaScript 對(duì)象包裹,賦予 observable 的能力,通過(guò)隱式訂閱,自動(dòng)跟蹤 observable 的變化。MobX 是觀察引用的,在跟蹤函數(shù)中(例如:computed value、reactions 等等),任何被引用的 observable 的屬性都會(huì)被記錄,一旦引用改變,MobX 將作出反應(yīng)。注意,不在跟蹤函數(shù)中的屬性將不會(huì)被跟蹤,在異步中訪問(wèn)的屬性也不會(huì)被跟蹤。
- Redux 的 state 是只讀的,只能通過(guò)將之前的 state 與觸發(fā)的 action 結(jié)合,產(chǎn)生新的 state,因此是純凈的(pure)。而 MobX 的 state 即可讀又可寫,action 是非必須的,可以直接賦值改變,因此是不純凈的(Impure)。
- Redux 需要你去規(guī)范化你的 state,Immutable 數(shù)據(jù)使 Reducer 在更新時(shí)需要將狀態(tài)樹的祖先數(shù)據(jù)進(jìn)行復(fù)制和更新,新的對(duì)象會(huì)導(dǎo)致與之 connect 的所有 UI 組件都重復(fù)渲染。因此 Redux state 不建議進(jìn)行深層嵌套,或者需要我們?cè)诮M件中用
shouldComponentUpdate
優(yōu)化。而 MobX 只自動(dòng)更新你所關(guān)心的,不必?fù)?dān)心嵌套帶來(lái)的重渲染問(wèn)題。 - 在 Redux 中區(qū)分有 smart 組件與 dumb 組件,dumb 負(fù)責(zé)展示,smart 負(fù)責(zé)狀態(tài)更新,數(shù)據(jù)獲取。而在 MobX 中無(wú)需區(qū)分,都是 smart,當(dāng)組件自身依賴的 observable 發(fā)生變化時(shí),會(huì)作出響應(yīng)。
Mobx 思想的實(shí)現(xiàn)原理
Mobx 最關(guān)鍵的函數(shù)在于 autoRun,autoRun 的專業(yè)名詞叫做依賴收集,也就是通過(guò)自然的使用,來(lái)收集依賴,當(dāng)變量改變時(shí),根據(jù)收集的依賴來(lái)判斷是否需要更新。Mobx 使用了 Object.defineProperty
攔截 getter 和 setter,和 Vue 一樣。
參考鏈接: mobx MobX 中文文檔
Vuex
Vuex 是專門為 Vue.js 設(shè)計(jì)的狀態(tài)管理庫(kù)。把組件的共享狀態(tài)抽取出來(lái),以一個(gè)全局單例模式管理。它采用集中式存儲(chǔ)管理應(yīng)用的所有組件的狀態(tài),并以相應(yīng)的規(guī)則保證狀態(tài)以一種可預(yù)測(cè)的方式發(fā)生變化。
核心概念
- State
- Getter
- Mutation
- Action
- Module
API
Vuex 的用法很簡(jiǎn)單,? 一句話總結(jié):commit mutation,dispatch action
?
參考鏈接:Vuex 官方文檔
有限狀態(tài)機(jī)(FSM)
?
有限狀態(tài)機(jī)(finite-state machine)又稱有限狀態(tài)自動(dòng)機(jī),簡(jiǎn)稱狀態(tài)機(jī),是表示有限個(gè)狀態(tài)以及在這些狀態(tài)之間的轉(zhuǎn)移和動(dòng)作等行為的數(shù)學(xué)模型,非常有用,可以模擬世界上大部分事物。
有限狀態(tài)機(jī)并不是一個(gè)復(fù)雜的概念,簡(jiǎn)單說(shuō),它有三個(gè)特征:
- 狀態(tài)總數(shù)(state)是有限的。
- 任一時(shí)刻,只處在一種狀態(tài)之中。
- 某種條件下,會(huì)從一種狀態(tài)轉(zhuǎn)變(transition)到另一種狀態(tài)。
總結(jié)
使用狀態(tài)去影響視圖,而 Action 主要負(fù)責(zé)完成狀態(tài)間的變更。代碼如何更好的構(gòu)建其核心在于使用最合理的狀態(tài)去管理界面,并用最合理的動(dòng)作去實(shí)現(xiàn)狀態(tài)間的變更。
所謂的狀態(tài)管理,實(shí)際上就是使用有限狀態(tài)機(jī)來(lái)管理前端狀態(tài)。
有限狀態(tài)機(jī)對(duì) JavaScript 的意義在于,很多對(duì)象可以寫成有限狀態(tài)機(jī)。
寫代碼之前,思考一下:
- 頁(yè)面有幾種狀態(tài)(初始化狀態(tài)?成功狀態(tài)?失敗狀態(tài)?出錯(cuò)狀態(tài)?)。
- 描述這些狀態(tài)需要什么參數(shù)。
- 在什么時(shí)候轉(zhuǎn)變狀態(tài),需要改變哪些部分。
然后跟著思路,完成數(shù)據(jù)與 UI 部分。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-839965.html
參考鏈接:javascript-state-machine xstate managing-state-in-javascript-with-state-machines-stent文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-839965.html
到了這里,關(guān)于前端狀態(tài)管理與有限狀態(tài)機(jī)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!