jiechen257

jiechen257

twitter
github

狀態管理工具的演變

概述:把組件之間需要共享的狀態抽取出來,遵循特定的約定,統一來管理,讓狀態的變化可以預測

Store 模式#

將狀態存到一個外部變量, this.$root.$data

var store = {
  state: {
    message: 'Hello!'
  },
  setMessageAction (newValue) {
    // 發生改變記錄點日誌啥的
    this.state.message = newValue
  },
  clearMessageAction () {
    this.state.message = ''
  }
}

由於 store 的 state 值改變方式(mutation)只有通過觸發 action 來操作,因此可以很容易跟蹤到 state 的改變流程,出現錯誤也能通過日誌明確錯誤位置

image

store並沒有限制組件只能通過action來修改state
由這一點演化得出了 Flux 架構

Flux#

Flux 是類似於 MVC、MVVM 之類的一種思想,它把一個應用分成四個部分
——View、Action、Dispatcher、Store

image

View 視圖層可以是通過 vue 或者 react 等框架實現,而 View 中的數據都是 Store,Store 改變就拋出一個事件,通知所有的訂閱者(或者監聽,不同的框架對應不同的數據響應技術)發生改變

Flux 要求,View 要想修改 Store,必須經過一套流程

  1. 視圖先要告訴 Dispatcher,讓 Dispatcher dispatch 一個 action
  2. Dispatcher 收到 View 發出的 action,然後轉發給 Store
  3. Store 就觸發相應的 action 來更新數據
  4. 數據更新則伴隨著 View 的更新

注意:

  • Dispatcher 的作用是接受所有的 Action。然後發給所有的 Store(Action 可能是 View 觸發的,也可能是其他地方觸發的,如測試用例)
  • Store 的改變只能通過 Action,Store 不應該有公開的 Setter,所有的 Setter 都應該是私有的,只能有公開的 Getter
  • 具體 Action 的處理邏輯一般放在 Store 里

Flux 特點: 單向流動

Redux#

與 Flux 思想類似,
但修改了 Flux 的一些特性:

  • 一個應用可以擁有多個 Store
  • 多個 Store 間可能存在依賴關係
  • Store 還封裝了處理數據的邏輯

image

Store#

Redux 裡面只有一個 Store,整個應用的數據都在這個大 Store 裡面。Store 的 State 不能直接修改,每次只能返回一個新的 State。Redux 整了個 createStore 函數來生成 Store。

import { createStore } from 'redux';
const store = createStore(fn);

Store 允許使用 store.subscribe 方法設置監聽函數,一旦 State 發生變化,就自動執行這個函數。這樣不管 View 是用什麼實現的,只要把 View 的更新函數 subscribe 一下,就可以實現 State 變化之後,View 自動渲染了。比如在 React 裡,把組件的 render 方法或 setState 方法訂閱進去就行。

Action#

和 Flux 一樣,Redux 裡面也有 Action,Action 就是 View 發出的通知,告訴 Store State 要改變。Action 必須有一個 type 屬性,代表 Action 的名稱,其他可以設置一堆屬性,作為參數供 State 變更時參考。

const action = {
  type: 'ADD_TODO',
  payload: 'Learn Redux'
};

Redux 可以用 Action Creator 批量來生成一些 Action。

Reducer#

Redux 沒有 Dispatcher 的概念,Store 裡面已經集成了 dispatch 方法。store.dispatch () 是 View 發出 Action 的唯一方法。

import { createStore } from 'redux';
const store = createStore(fn);

store.dispatch({
  type: 'ADD_TODO',
  payload: 'Learn Redux'
});

Redux 用一個叫做 Reducer 的純函數來處理事件。Store 收到 Action 以後,必須給出一個新的 State(就是剛才說的 Store 的 State 不能直接修改,每次只能返回一個新的 State),這樣 View 才會發生變化。這種 State 的計算過程就叫做 Reduce

純函數,即沒有任何副作用

  • 對於相同的輸入,永遠都只會有相同發輸出
  • 不會影響挖補的變量,也不會被外部變量影響
  • 不能改寫參數

Redux 根據應用的狀態和當前的 action 推導出新的 state:(previousState, action) => newState

類比 Flux:(state, action) => state

question: 為什麼叫做 Reducer 呢?
—— reduce 是一個函數式編程的概念,經常和 map 放在一起說,簡單來說,map 就是映射,reduce 就是歸納。
映射就是把一個列表按照一定規則映射成另一個列表,而 reduce 是把一個列表通過一定規則進行合併,也可以理解為對初始值進行一系列的操作,返回一個新的值

整體流程#

1、用戶通過 View 發出 Action:

store.dispatch(action);

2、然後 Store 自動調用 Reducer,並且傳入兩個參數:當前 State 和收到的 Action。 Reducer 會返回新的 State 。

let nextState = xxxReducer(previousState, action);

3、State 一旦有變化,Store 就會調用監聽函數。

store.subscribe(listener);

4、listener 可以通過 store.getState () 得到當前狀態。如果使用的是 React,這時可以觸發重新渲染 View。

function listerner() {
  let newState = store.getState();
  component.setState(newState);   
}

對比 Flux#

和 Flux 比較一下:Flux 中 Store 是各自為戰的,每個 Store 只對對應的 View 負責,每次更新都只通知對應的 View:

image

Redux 中各子 Reducer 都是由根 Reducer 統一管理的,每個子 Reducer 的變化都要經過根 Reducer 的整合:

image

簡單來說,Redux 有三大原則: 單一數據源:Flux 的數據源可以是多個。 State 是只讀的:Flux 的 State 可以隨便改。 * 使用純函數來執行修改:Flux 執行修改的不一定是純函數。

Redux 和 Flux 一樣都是單向數據流

與 React 關係#

Redux 和 Flux 類似,只是一種思想或者規範,它和 React 之間沒有關係。Redux 支持 React、Angular、Ember、jQuery 甚至純 JavaScript。

但是因為 React 包含函數式的思想,也是單向數據流,和 Redux 很搭,所以一般都用 Redux 來進行狀態管理。為了簡單處理 Redux 和 React UI 的綁定,一般通過一個叫 react-redux 的庫和 React 配合使用,這個是 react 官方出的(如果不用 react-redux,那麼手動處理 Redux 和 UI 的綁定,需要寫很多重複的代碼,很容易出錯,而且有很多 UI 渲染邏輯的優化不一定能處理好)。

Redux 將 React 組件分為容器型組件和展示型組件,容器型組件一般通過 connect 函數生成,它訂閱了全局狀態的變化,通過 mapStateToProps 函數,可以對全局狀態進行過濾,而展示型組件不直接從 global state 獲取數據,其數據來源於父組件。

image

如果一個組件既需要 UI 呈現,又需要業務邏輯處理,那就得拆,拆成一個容器組件包著一個展示組件。

Redux-saga#

Redux 處理異步操作,添加中間件後的產物

官方文檔:Redux-Saga

Dva#

官方定義:dva 首先是一個基於 reduxredux-saga 的數據流方案,然後為了簡化開發體驗,dva 還額外內置了 react-routerfetch,所以也可以理解為一個輕量級的應用框架

簡單理解,就是讓使用 react-redux 和 redux-saga 編寫的代碼組織起來更合理,維護起來更方便

之前我們聊了 redux、react-redux、redux-saga 之類的概念,大家肯定覺得頭昏腦脹的,什麼 action、reducer、saga 之類的,寫一個功能要在這些 js 文件裡面不停的切換。dva 做的事情很簡單,就是讓這些東西可以寫到一起,不用分開來寫了

比如:

// 以前書寫的方式是創建 sagas/products.js, reducers/products.js 和actions/products.js,然後把 saga、action、reducer 啥的分開來寫,來回切換

app.model({
  // namespace - 對應 reducer 在 combine 到 rootReducer 時的 key 值
  namespace: 'products',
  // state - 對應 reducer 的 initialState
  state: {
    list: [],
    loading: false,
  },
  // subscription - 在 dom ready 後執行
  subscriptions: [
    function(dispatch) {
      dispatch({type: 'products/query'});
    },
  ],
  // effects - 對應 saga,並簡化了使用
  effects: {
    ['products/query']: function*() {
      yield call(delay(800));
      yield put({
        type: 'products/query/success',
        payload: ['ant-tool', 'roof'],
      });
    },
  },
  // reducers - 就是傳統的 reducers
  reducers: {
    ['products/query'](state) {
      return { ...state, loading: true, };
    },
    ['products/query/success'](state, { payload }) {
      return { ...state, loading: false, list: payload };
    },
  },
});

MobX#

官網:MobX 中文文檔

對比 Flux 體系的單向數據流方案,Mobx 的思想則是 :任何源自應用狀態的東西都應該自動地獲得 —— 狀態只要一變,其他用到狀態的地方就都跟著自動變

image

Flux 或者說 Redux 的思想主要就是函數式編程(FP)的思想,所以學習起來會覺得累一些。而 MobX 更接近於面向對象編程,它把 state 包裝成可觀察的對象,這個對象會驅動各種改變。什麼是可觀察?就是 MobX 老大哥在看著 state 呢。state 只要一改變,所有用到它的地方就都跟著改變了。這樣整個 View 可以被 state 來驅動。

const obj = observable({
    a: 1,
    b: 2
})

autoRun(() => {
    console.log(obj.a)
})

obj.b = 3 // 什麼都沒有發生
obj.a = 2 // observe 函數的回調觸發了,控制台輸出:2

上面的 obj,他的 obj.a 屬性被使用了,那麼只要 obj.a 屬性一變,所有使用的地方都會被調用。autoRun 就是這個老大哥,他看著所有依賴 obj.a 的地方,也就是收集所有對 obj.a 的依賴。當 obj.a 改變時,老大哥就會觸發所有依賴去更新

MobX 和 Flux、Redux 一樣,都是和具體的前端框架無關的,也就是說可以用於 React(mobx-react) 或者 Vue(mobx-vue)。一般來說,用到 React 比較常見,很少用於 Vue,因為 Vuex 本身就類似 MobX,很靈活。如果我們把 MobX 用於 React 或者 Vue,可以看到很多 setState () 和 this.state.xxx = 這樣的處理都可以省了。

參考#

Vuex、Flux、Redux、Redux-saga、Dva、MobX

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。