前端狀態管理與有限狀態機

當下前端流行的框架,都是用狀態來描述界面(state => view),能夠說前端開發實際上就是在維護各類狀態(state),這已經成爲目前前端開發的共識。javascript

View = ViewModel(Model);
複製代碼

理想狀況下,ViewModel 是純函數,給定相同的 Model,產出相同的 View。前端

state => view 很好理解,但如何在 view 中合理地修改 state 也是一個問題。vue

爲何須要狀態管理

舉個例子

圖書館的管理,原來是開放式的,全部人能夠隨意進出書庫借書還書,若是人數很少,這種方式能夠減小流程,增長效率,一旦人數變多就勢必形成混亂。java

Flux 就像是給這個圖書館加上了一個管理員,全部借書還書的行爲都須要委託管理員去作,管理員會規範對書庫的操做行爲,也會記錄每一個人的操做,減小混亂的現象。react

一個比喻

咱們寄一件東西的過程git

沒有快遞時:github

  • 打包準備好要送出去的東西
  • 直接到朋友家,把東西送給朋友
  • 很直接很方便,很費時間

有了快遞公司:vuex

  • 打包準備好要送出去的東西
  • 到快遞公司,填寫物品,收件人等基本信息
  • 快遞公司替你送物品到你的朋友家,咱們的工做結束了

多了快遞公司,讓快遞公司給咱們送快遞。數據庫

當咱們只寄送物品給一個朋友,次數較少,物品又較少的時候,咱們直接去朋友家就挺好的。但當咱們要頻繁寄送給不少朋友不少商品的時候,問題就複雜了。編程

軟件工程的本質便是管理複雜度。使用狀態管理類框架會有必定的學習成本並且一般會把簡單的事情作複雜,但若是咱們想作複雜一點的事情(同時寄不少物品到多個不一樣地址),對咱們來講,快遞會讓複雜的事情變的簡單。

這同時也解釋了,是否須要添加狀態管理框架,咱們能夠根據本身的業務實際狀況和技術團隊的偏好而添加,有些狀況下,建立一個全局對象就能解決不少問題。

核心思想

Flux 的核心思想:數據單向流動。

  • 不一樣組件的 state,存放在一個外部的、公共的 Store 上面。
  • 組件訂閱 Store 的不一樣部分。
  • 組件發送(dispatch)動做(action),引起 Store 的更新。

Redux 的核心概念

  • 全部的狀態存放在 Store。組件每次從新渲染,都必須由狀態變化引發。
  • 用戶在 UI 上發出 action。
  • reducer 函數接收 action,而後根據當前的 state,計算出新的 state。

Redux store 是單一數據源。Redux 沒有 dispatcher 的概念,轉而使用純函數(pure function)代替。

Redux store 是不可變的(Immutable)。

MobX

  • Observable:它的 state 是可被觀察的,不管是基本數據類型仍是引用數據類型,均可以使用 MobX 的 (@)observable 來轉變爲 observable value。
  • Reactions:它包含不一樣的概念,基於被觀察數據的更新致使某個計算值(computed values),或者是發送網絡請求以及更新視圖等,都屬於響應的範疇,這也是響應式編程(Reactive Programming)在 JavaScript 中的一個應用。
  • Actions:它至關於全部響應的源頭,例如用戶在視圖上的操做,或是某個網絡請求的響應致使的被觀察數據的變動。

和 Redux 對單向數據流的嚴格規範不一樣,Mobx 只專一於從 store 到 view 的過程。在 Redux 中,數據的變動須要監聽,而 Mobx 的數據依賴是基於運行時的,這點和 Vuex 更爲接近。

Vuex 的狀態管理模式

  • state,驅動應用的數據源;
  • view,以聲明方式將 state 映射到視圖;
  • actions,響應在 view 上的用戶輸入致使的狀態變化。

Flux

Facebook 提出了 Flux 架構思想,規範了數據在應用中的流動方式。其基本架構以下入所示,其核心理念是單向數據流,它完善了 React 對應用狀態的管理。

上圖描述了頁面的啓動和運行原理:

1.經過 dispatcher 派發 action,並利用 store 中的 action 處理邏輯更新狀態和 view

2.而 view 也能夠觸發新的 action,從而進入新的步驟 1

其中的 action 是用於描述動做的簡單對象,一般經過用戶對 view 的操做產生,包括動做類型和動做所攜帶的所需參數,好比描述刪除列表項的 action:

{
    type: types.DELETE_ITEM,
    id: id
};
複製代碼

而 dispatcher 用於對 action 進行分發,分發的目標就是註冊在 store 裏的事件處理函數:

dispatcher.register(function(action) {
  switch (action.type) {
    case "DELETE_ITEM":
      sotre.deleteItem(action.id); //更新狀態
      store.emitItemDeleted(); //通知視圖更新
      break;
    default:
    // no op
  }
});
複製代碼

store 包含了應用的全部狀態和邏輯,它有點像傳統的 MVC 模型中的 model 層,但又與之有明顯的區別,store 包括的是一個應用特定功能的所有狀態和邏輯,它表明了應用的整個邏輯層;而不是像 Model 同樣包含的是數據庫中的一些記錄和與之對應的邏輯。

參考連接:flux

Redux

原生 Redux API 最簡單的用例

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);
});
複製代碼

應用中全部的 state 都以一個對象樹的形式儲存在一個單一的 store 中。 改變 state 的惟一辦法是觸發 action,一個描述發生什麼的對象。 爲了描述 action 如何改變 state 樹,你須要編寫 reducers。

Redux 三大原則

  • 單一數據源

    整個應用的 state 被儲存在一棵 object tree 中,而且這個 object tree 只存在於惟一一個 store 中。

  • state 是隻讀的

    惟一改變 state 的方法就是觸發 action,action 是一個用於描述已發生事件的普通對象。

  • 使用純函數來執行修改

    爲了描述 action 如何改變 state tree ,你須要編寫 reducers。 改變 state 的唯一方法是 dispatch action。你也能夠 subscribe 監聽 state 的變化,而後更新 UI。

嚴格的單向數據流是 Redux 架構的設計核心。

Redux API

Redux 的 API 很是少。

Redux 定義了一系列的約定(contract)來讓你來實現(例如 reducers),同時提供少許輔助函數來把這些約定整合到一塊兒。

Redux 只關心如何管理 state。在實際的項目中,你還須要使用 UI 綁定庫如 react-redux。

  • createStore(reducer, [preloadedState], [enhancer])
  • combineReducers(reducers)
  • bindActionCreators(actionCreators, dispatch)
  • applyMiddleware(...middlewares)
  • compose(...functions)

immutable

在寫 redux 的 action 的時候,老是須要用到擴展語句或者 Object.assign()的方式來獲得一個新的 state,這一點對於 JavaScript 而言是對象的淺拷貝,它對內存的開銷確定是大於 mobX 中那樣直接操做對象屬性的方式大得多。

參考連接:redux-immutable seamless-immutable reselect 爲何使用 Redux 管理狀態是可預測的

redux-saga

redux 是 react 技術棧中的狀態控制流框架,使用了標準的函數式思想,指望(強制)全部狀態管理都是純函數。這也意味着各狀態之間都是獨立的。可是有一類狀態 redux 直接甩給了的第三方模塊,反作用模塊 redux-saga 也就成了不辭辛苦的典型表明。反作用正是由於不肯定性和可變性而得名,而其給出的狀態又是相互影響,如何解耦使得本來複雜的非線性呈現爲線性邏輯,正是有限狀態機的用武之處。

DvaJS

dva 首先是一個基於 reduxredux-saga 的數據流方案,而後爲了簡化開發體驗,dva 還額外內置了 react-routerfetch,因此也能夠理解爲一個輕量級的應用框架。

在 redux 的生態圈內,每一個環節有多種方案,好比 Data 能夠是 immutable 或者 plain object,在你選了 immutable 以後,用 immutable.js 仍是 seamless-immutable,以及是否用 redux-immutable 來輔助數據修改,都須要選擇。

參考連接:Redux 中文文檔 immutable-js immer dvajs React + Redux 最佳實踐

MobX

MobX 是一個用法簡單優雅、同時具備可擴展性的狀態管理庫。

一個簡單的例子

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 中咱們能夠直接修改狀態

import { observable } from "mobx";

const appState = observable({ counter: 0 });

appState.counter += 1;
複製代碼

能夠經過引入 Strict 模式來避免這種不良好的實踐:

import { useStrict } from "mobx";

useStrict(true);
複製代碼

MobX 脫胎於響應式編程(Reactive Programming),其核心思想爲 Anything that can be derived from the application state, should be derived. Automatically,即避免任何的重複狀態。

MobX 中核心的概念便是 Observable,相信接觸過響應式編程的確定很是熟悉,從後端的典型表明 RxJava 到 Android/iOS 開發中的各類響應式框架都各領風騷。

與 Redux 狀態管理上的異同

Redux / MobX 均爲客戶端開源狀態管理庫,用狀態來描述 UI 界面,它們與 React 都不具備強綁定關係,你也能夠配合別的框架來使用它們。 固然,與 React 是再合適不過的了,React 做爲 View 層的框架,經過 Virtual DOM 機制來優化 UI 渲染,Redux / MobX 則提供了將相應狀態同步到 React 的機制。

Redux 與 MobX 的不一樣主要集中於如下幾點:

  • Redux 是單一數據源,而 MobX 每每是多個 store。MobX 能夠根據應用的 UI、數據或業務邏輯來組織 store,具體如何進行須要你本身進行權衡。
  • Redux store 使用普通的 JavaScript 對象結構,MobX 將常規 JavaScript 對象包裹,賦予 observable 的能力,經過隱式訂閱,自動跟蹤 observable 的變化。MobX 是觀察引用的,在跟蹤函數中(例如:computed value、reactions 等等),任何被引用的 observable 的屬性都會被記錄,一旦引用改變,MobX 將做出反應。注意,不在跟蹤函數中的屬性將不會被跟蹤,在異步中訪問的屬性也不會被跟蹤。
  • Redux 的 state 是隻讀的,只能經過將以前的 state 與觸發的 action 結合,產生新的 state,所以是純淨的(pure)。而 MobX 的 state 便可讀又可寫,action 是非必須的,能夠直接賦值改變,所以是不純淨的(Impure)。
  • Redux 須要你去規範化你的 state,Immutable 數據使 Reducer 在更新時須要將狀態樹的祖先數據進行復制和更新,新的對象會致使與之 connect 的全部 UI 組件都重複渲染。所以 Redux state 不建議進行深層嵌套,或者須要咱們在組件中用 shouldComponentUpdate 優化。而 MobX 只自動更新你所關心的,沒必要擔憂嵌套帶來的重渲染問題。
  • 在 Redux 中區分有 smart 組件與 dumb 組件,dumb 負責展現,smart 負責狀態更新,數據獲取。而在 MobX 中無需區分,都是 smart,當組件自身依賴的 observable 發生變化時,會做出響應。

Mobx 思想的實現原理

Mobx 最關鍵的函數在於 autoRun,autoRun 的專業名詞叫作依賴收集,也就是經過天然的使用,來收集依賴,當變量改變時,根據收集的依賴來判斷是否須要更新。Mobx 使用了 Object.defineProperty 攔截 getter 和 setter,和 Vue 同樣。

參考連接: mobx MobX 中文文檔

Vuex

Vuex 是專門爲 Vue.js 設計的狀態管理庫。把組件的共享狀態抽取出來,以一個全局單例模式管理。它採用集中式存儲管理應用的全部組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。

核心概念

  • State
  • Getter
  • Mutation
  • Action
  • Module

API

Vuex 的用法很簡單,  一句話總結:commit mutation,dispatch action

參考連接:Vuex 官方文檔

有限狀態機(FSM)

有限狀態機(finite-state machine)又稱有限狀態自動機,簡稱狀態機,是表示有限個狀態以及在這些狀態之間的轉移和動做等行爲的數學模型,很是有用,能夠模擬世界上大部分事物。

有限狀態機並非一個複雜的概念,簡單說,它有三個特徵:

  • 狀態總數(state)是有限的。
  • 任一時刻,只處在一種狀態之中。
  • 某種條件下,會從一種狀態轉變(transition)到另外一種狀態。

總結

使用狀態去影響視圖,而 Action 主要負責完成狀態間的變動。代碼如何更好的構建其核心在於使用最合理的狀態去管理界面,並用最合理的動做去實現狀態間的變動。

所謂的狀態管理,實際上就是使用有限狀態機來管理前端狀態。

有限狀態機對 JavaScript 的意義在於,不少對象能夠寫成有限狀態機。

寫代碼以前,思考一下:

  • 頁面有幾種狀態(初始化狀態?成功狀態?失敗狀態?出錯狀態?)。
  • 描述這些狀態須要什麼參數。
  • 在何時轉變狀態,須要改變哪些部分。

而後跟着思路,完成數據與 UI 部分。

參考連接:javascript-state-machine xstate managing-state-in-javascript-with-state-machines-stent

相關文章
相關標籤/搜索