探一探前端的狀態管理

有關狀態管理這個詞兒已經出現好幾年了。不過實際中大多數項目其實還用不上狀態管理,並且即便在項目中用上,但也用得並很差,尤爲是初學者,更多的是去記錄各類API的用法,殊不知其核心思想,很難快速實現狀態管理的最佳實踐。javascript

什麼是「狀態管理」

以前在 Vue組件通訊事件總結 這篇文章裏研究過Vue裏面各個組件之間的數據通訊,通常頁面不是特別複雜的項目,使用這篇文章總結的通訊方式足夠了,但當前端頁面複雜度愈來愈高,props$emit$listenersref / refseventBus這些方法也許還不夠方便,這個時候能夠開始嘗試vuex的狀態管理機制了。前端

用一個通俗易懂的概念來解釋狀態管理:全局變量。vue

可是在項目的開發中,咱們應該都記得前輩的叮囑:儘可能不要或少用全局變量,由於那是不可控的操做,任意的操做均可能致使數據的改變,形成全局污染,沒法追蹤數據變化過程,因此開發中咱們小菜鳥也大都避免去過多的操做全局變量。java

但當項目複雜度比較高時,也許多個組件、多個頁面之間須要實現一種數據或狀態的共享,這時候,咱們就能夠將這些狀態統一的進行管理,既能夠實現共享狀態,又能追蹤到數據的變化,可能這也是狀態管理誕生的一種緣由吧。vuex

固然,跟全局變量同樣,各個文章裏面也是叮而囑之,若是沒有哦必要,儘可能不要使用狀態管理。編程

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

Flux 架構思想

關於如何在UI試圖中合理地修改state,Facebook提出了flux思想。redux

Flux 是一種架構思想,專門解決軟件的結構問題。後來的rudux和vux也都是由Flux的思想演化出來的。數據結構

Flux將一個應用分紅四個部分: View(視圖)、Action(動做)、Dispatcher(派發)、Store(數據)架構

總體流程:框架

這是一個單向流動的過程,

用戶訪問view -> 觸發事件action -> Dispatcher知道後告知store要更新 -> store更新,觸發視圖View更新頁面

特色:

  • 單向數據流
  • Store 能夠有多個
  • Store 不只存放數據,還封裝了處理數據的方法(便可以直接修改數據狀態)

Redux

redux的核心概念:

Redux store 是單一數據源,且store是不可變的,Redux 沒有 dispatcher 的概念

特色:

  • 單向數據流
  • 單一數據源,只有一個store
  • state只讀
  • 沒有 Dispatcher ,而是在 Store 中集成了 dispatch 方法,store.dispatch() 是 View 發出 Action 的惟一途徑

Store 中提供了幾個管理 state 的 API:

  • store.getState():獲取當前 state
  • store.dispatch(action):觸發 state 改變(惟一途徑)
  • store.subscribe(listener):設置 state 變化的監聽函數(若把視圖更新函數做爲 listener 傳入,則可觸發視圖自動渲染)

總體流程:

Action Creator -> 觸發action -> store.dispatch(action) -> reducer(state, action) -> 更新state

MobX

MobX 是經過透明的函數響應式編程使得狀態管理變得簡單和可擴展,是一個用法簡單優雅、同時具備可擴展性的狀態管理庫。

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

若是你們使用過 Vue 的話相信對其雙向綁定 MVVM 的思想並不陌生,React + Mobx 至關因而 Vue 全局做用域下的雙向綁定,而 Vue 的狀態管理框架 Vuex 倒是借鑑了 Flux 架構,連尤大都說,彷佛有點你中有我,我中有你的關係。

特色:

  • 每每是多個 Store
  • 數據流流動不天然,只有用到的數據纔會引起綁定,局部精確更新
  • 通常適合應用於中小型項目中

Vuex

仍然放一張vuex的示意圖:

vuex的運做原理跟redux稍有不一樣。由於Vue雖然是單向數據流,但Vue 基於 ES5 中的 getter/setter 來實現視圖和數據的雙向綁定,所以 Vuex 中 state 的變動能夠經過 setter 通知到視圖中對應的指令來實現視圖更新。且state不是經過actions來修改的,而是經過mutations。

特色:

  • 單向數據流, 經過 store.dispatch() 調用 Action ,在 Action 執行完異步操做以後經過 store.commit() 調用 Mutation 更新 State

  • 單一數據源

  • actions 能夠是 異步操做,故可在action中調用後臺接口獲取新的數據;

  • mutations 只能是 同步操做;

  • mutations 和 actions 均可直接更改 state,可是當 action 含有異步操做時,會使得數據變化混亂,難以跟蹤,使得調試困難;基於以上緣由,Vuex 規定只能是 mutations 來改變 state。

總體流程:

用戶訪問view -> 觸發事件action ->action中觸發對應的mutation -> 在mutation函數中改變state -> 經過 getter/setter 實現的雙向綁定的機制,視圖View會自動更新頁面

使用一句話總結:commit mutation,dispatch action

幾種方案的異同

從上面幾種熱門的狀態管理實現方式來看,能夠發現,這幾種數據流模型幾乎都是從 action 到 view 之間的一種數據流動,總的過程能夠大體簡化爲:

action -> 更新 state(視圖層)

一、 數據狀態中心: state和store

能夠發現,在redux和flux中,數據狀態用store表示,而在vuex和mobx中,這個store被state替代。

和 Redux 中使用不可變數據來表示 state 不一樣,Vuex 中沒有 reducer 來生成全新的 state 來替換舊的 state,Vuex 中的 state 是能夠被修改的

Vuex 中的 state 是可修改的,而修改 state 的方式不是經過 actions,而是經過 mutations。更改 Vuex 的 store 中的狀態的惟一方法是提交 mutation。

二、 行爲action

咱們想要更新視圖內容,第一步就是要有觸發的動做,action就是向store發起更新請求的最小單元。但action並不是是一個動詞,它是一個行爲的描述,本質上是一個純聲明式的數據結構,僅提供對事件的描述,不提供事件的具體邏輯

在rudux中,生成action有兩種方式:

  • 聲明對象:

    const action = {
      type: 'ADD_TODO',
      text: '生成一個action'
    }
    dispatch(action)
    複製代碼
  • 函數生成:

    function addTodo(text) {
      return {
        type: ADD_TODO,
        text
      }
    }
    dispatch(addTodo())
    複製代碼

三、dispatch

在各種應用狀態管理的模型中,一般都會有一個dispatch方法,它就聲明在Store上,負責調用各個Action,而後由Store上對應的分發機制進行處理。

四、Reducer / Mutation

如今到了咱們的第三步,更新狀態。flux和mobx對狀態更新的處理是直接更改,而redux和vuex中間還會多出一個reducer和mutation來處理更新的工做。

至於reducer和mutation,分別是針對redux和vuex的不一樣工做機制。

先看reducer,Actions 只能描述發生了什麼,並不能描述狀態發生了什麼變化,Reducers 指定 state tree 將要發生什麼,它主要的工做內容是:接受一個action,而後經過switch匹配action的type,做出相應的處理後,返回一個新的對象。爲何是新的對象呢?由於redux最核心的一個概念,store是不可變的, Redux 是一個實踐函數式編程(FP)理念的庫。下面是一個最簡單的reducer:

const items = (state = [], action) => {
  switch (action.type) {
    case "ADD_ITEM":
      return [...state, { text: action.text }]
    default:
      return state
  }
}
複製代碼

再來看mutation,一個 mutation 是由一個 type 和與其對應的 handler 構成的,type 是一個字符串類型用以做爲 key 去識別具體的某個 mutation,handler 則是對 state 實際進行變動的函數。

// store
const store = {
  books: []
}

// mutations
const mutations = {
  [ADD_BOOKS](state, book) {
    state.books.push(book)
  }
}
複製代碼
相關文章
相關標籤/搜索