概述:把組件之間需要共享的狀態抽取出來,遵循特定的約定,統一來管理,讓狀態的變化可以預測
Store 模式#
將狀態存到一個外部變量, this.$root.$data
var store = {
state: {
message: 'Hello!'
},
setMessageAction (newValue) {
// 發生改變記錄點日誌啥的
this.state.message = newValue
},
clearMessageAction () {
this.state.message = ''
}
}
由於 store 的 state 值改變方式(mutation)只有通過觸發 action 來操作,因此可以很容易跟蹤到 state 的改變流程,出現錯誤也能通過日誌明確錯誤位置
store並沒有限制組件只能通過action來修改state
由這一點演化得出了 Flux 架構
Flux#
Flux 是類似於 MVC、MVVM 之類的一種思想,它把一個應用分成四個部分
——View、Action、Dispatcher、Store
View 視圖層可以是通過 vue 或者 react 等框架實現,而 View 中的數據都是 Store,Store 改變就拋出一個事件,通知所有的訂閱者(或者監聽,不同的框架對應不同的數據響應技術)發生改變
Flux 要求,View 要想修改 Store,必須經過一套流程
- 視圖先要告訴 Dispatcher,讓 Dispatcher dispatch 一個 action
- Dispatcher 收到 View 發出的 action,然後轉發給 Store
- Store 就觸發相應的 action 來更新數據
- 數據更新則伴隨著 View 的更新
注意:
- Dispatcher 的作用是接受所有的 Action。然後發給所有的 Store(Action 可能是 View 觸發的,也可能是其他地方觸發的,如測試用例)
- Store 的改變只能通過 Action,Store 不應該有公開的 Setter,所有的 Setter 都應該是私有的,只能有公開的 Getter
- 具體 Action 的處理邏輯一般放在 Store 里
Flux 特點: 單向流動
Redux#
與 Flux 思想類似,
但修改了 Flux 的一些特性:
- 一個應用可以擁有多個 Store
- 多個 Store 間可能存在依賴關係
- Store 還封裝了處理數據的邏輯
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:
Redux 中各子 Reducer 都是由根 Reducer 統一管理的,每個子 Reducer 的變化都要經過根 Reducer 的整合:
簡單來說,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 獲取數據,其數據來源於父組件。
如果一個組件既需要 UI 呈現,又需要業務邏輯處理,那就得拆,拆成一個容器組件包著一個展示組件。
Redux-saga#
Redux 處理異步操作,添加中間件後的產物
官方文檔:Redux-Saga
Dva#
官方定義:dva 首先是一個基於 redux 和 redux-saga 的數據流方案,然後為了簡化開發體驗,dva 還額外內置了 react-router 和 fetch,所以也可以理解為一個輕量級的應用框架
簡單理解,就是讓使用 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 的思想則是 :任何源自應用狀態的東西都應該自動地獲得 —— 狀態只要一變,其他用到狀態的地方就都跟著自動變
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 = 這樣的處理都可以省了。