摘要:也就是說(shuō)不應(yīng)該有公開(kāi)的,所有都應(yīng)該是私有的,只能有公開(kāi)的。允許使用方法設(shè)置監(jiān)聽(tīng)函數(shù),一旦發(fā)生變化,就自動(dòng)執(zhí)行這個(gè)函數(shù)。用一個(gè)叫做的純函數(shù)來(lái)處理事件??梢酝ㄟ^(guò)得到當(dāng)前狀態(tài)。在中,同步的表現(xiàn)就是發(fā)出以后,立即算出。
這篇文章試著聊明白這一堆看起來(lái)挺復(fù)雜的東西。在聊之前,大家要始終記得一句話(huà):一切前端概念,都是紙老虎。
不管是Vue,還是 React,都需要管理狀態(tài)(state),比如組件之間都有共享狀態(tài)的需要。什么是共享狀態(tài)?比如一個(gè)組件需要使用另一個(gè)組件的狀態(tài),或者一個(gè)組件需要改變另一個(gè)組件的狀態(tài),都是共享狀態(tài)。
父子組件之間,兄弟組件之間共享狀態(tài),往往需要寫(xiě)很多沒(méi)有必要的代碼,比如把狀態(tài)提升到父組件里,或者給兄弟組件寫(xiě)一個(gè)父組件,聽(tīng)聽(tīng)就覺(jué)得挺啰嗦。
如果不對(duì)狀態(tài)進(jìn)行有效的管理,狀態(tài)在什么時(shí)候,由于什么原因,如何變化就會(huì)不受控制,就很難跟蹤和測(cè)試了。如果沒(méi)有經(jīng)歷過(guò)這方面的困擾,可以簡(jiǎn)單理解為會(huì)搞得很亂就對(duì)了。
在軟件開(kāi)發(fā)里,有些通用的思想,比如隔離變化,約定優(yōu)于配置等,隔離變化就是說(shuō)做好抽象,把一些容易變化的地方找到共性,隔離出來(lái),不要去影響其他的代碼。約定優(yōu)于配置就是很多東西我們不一定要寫(xiě)一大堆的配置,比如我們幾個(gè)人約定,view 文件夾里只能放視圖,不能放過(guò)濾器,過(guò)濾器必須放到 filter 文件夾里,那這就是一種約定,約定好之后,我們就不用寫(xiě)一大堆配置文件了,我們要找所有的視圖,直接從 view 文件夾里找就行。
根據(jù)這些思想,對(duì)于狀態(tài)管理的解決思路就是:把組件之間需要共享的狀態(tài)抽取出來(lái),遵循特定的約定,統(tǒng)一來(lái)管理,讓狀態(tài)的變化可以預(yù)測(cè)。根據(jù)這個(gè)思路,產(chǎn)生了很多的模式和庫(kù),我們來(lái)挨個(gè)聊聊。
Store 模式最簡(jiǎn)單的處理就是把狀態(tài)存到一個(gè)外部變量里面,比如:this.$root.$data,當(dāng)然也可以是一個(gè)全局變量。但是這樣有一個(gè)問(wèn)題,就是數(shù)據(jù)改變后,不會(huì)留下變更過(guò)的記錄,這樣不利于調(diào)試。
所以我們稍微搞得復(fù)雜一點(diǎn),用一個(gè)簡(jiǎn)單的 Store 模式:
var store = { state: { message: "Hello!" }, setMessageAction (newValue) { // 發(fā)生改變記錄點(diǎn)日志啥的 this.state.message = newValue }, clearMessageAction () { this.state.message = "" } }
store 的 state 來(lái)存數(shù)據(jù),store 里面有一堆的 action,這些 action 來(lái)控制 state 的改變,也就是不直接去對(duì) state 做改變,而是通過(guò) action 來(lái)改變,因?yàn)槎甲?action,我們就可以知道到底改變(mutation)是如何被觸發(fā)的,出現(xiàn)錯(cuò)誤,也可以記錄記錄日志啥的。
不過(guò)這里沒(méi)有限制組件里面不能修改 store 里面的 state,萬(wàn)一組件瞎胡修改,不通過(guò) action,那我們也沒(méi)法跟蹤這些修改是怎么發(fā)生的。所以就需要規(guī)定一下,組件不允許直接修改屬于 store 實(shí)例的 state,組件必須通過(guò) action 來(lái)改變 state,也就是說(shuō),組件里面應(yīng)該執(zhí)行 action 來(lái)分發(fā) (dispatch) 事件通知 store 去改變。這樣約定的好處是,我們能夠記錄所有 store 中發(fā)生的 state 改變,同時(shí)實(shí)現(xiàn)能做到記錄變更 (mutation)、保存狀態(tài)快照、歷史回滾/時(shí)光旅行的先進(jìn)的調(diào)試工具。
這樣進(jìn)化了一下,一個(gè)簡(jiǎn)單的 Flux 架構(gòu)就實(shí)現(xiàn)了。
FluxFlux其實(shí)是一種思想,就像MVC,MVVM之類(lèi)的,他給出了一些基本概念,所有的框架都可以根據(jù)他的思想來(lái)做一些實(shí)現(xiàn)。
Flux把一個(gè)應(yīng)用分成了4個(gè)部分:
View
Action
Dispatcher
Store
比如我們搞一個(gè)應(yīng)用,顯而易見(jiàn),這個(gè)應(yīng)用里面會(huì)有一堆的 View,這個(gè) View 可以是Vue的,也可以是 React的,啥框架都行,啥技術(shù)都行。
View 肯定是要展示數(shù)據(jù)的,所謂的數(shù)據(jù),就是 Store,Store 很容易明白,就是存數(shù)據(jù)的地方。當(dāng)然我們可以把 Store 都放到一起,也可以分開(kāi)來(lái)放,所以就有一堆的 Store。但是這些 View 都有一個(gè)特點(diǎn),就是 Store 變了得跟著變。
View 怎么跟著變呢?一般 Store 一旦發(fā)生改變,都會(huì)往外面發(fā)送一個(gè)事件,比如 change,通知所有的訂閱者。View 通過(guò)訂閱也好,監(jiān)聽(tīng)也好,不同的框架有不同的技術(shù),反正 Store 變了,View 就會(huì)變。
View 不是光用來(lái)看的,一般都會(huì)有用戶(hù)操作,用戶(hù)點(diǎn)個(gè)按鈕,改個(gè)表單啥的,就需要修改 Store。Flux 要求,View 要想修改 Store,必須經(jīng)過(guò)一套流程,有點(diǎn)像我們剛才 Store 模式里面說(shuō)的那樣。視圖先要告訴 Dispatcher,讓 Dispatcher dispatch 一個(gè) action,Dispatcher 就像是個(gè)中轉(zhuǎn)站,收到 View 發(fā)出的 action,然后轉(zhuǎn)發(fā)給 Store。比如新建一個(gè)用戶(hù),View 會(huì)發(fā)出一個(gè)叫 addUser 的 action 通過(guò) Dispatcher 來(lái)轉(zhuǎn)發(fā),Dispatcher 會(huì)把 addUser 這個(gè) action 發(fā)給所有的 store,store 就會(huì)觸發(fā) addUser 這個(gè) action,來(lái)更新數(shù)據(jù)。數(shù)據(jù)一更新,那么 View 也就跟著更新了。
這個(gè)過(guò)程有幾個(gè)需要注意的點(diǎn):
Dispatcher 的作用是接收所有的 Action,然后發(fā)給所有的 Store。這里的 Action 可能是 View 觸發(fā)的,也有可能是其他地方觸發(fā)的,比如測(cè)試用例。轉(zhuǎn)發(fā)的話(huà)也不是轉(zhuǎn)發(fā)給某個(gè) Store,而是所有 Store。
Store 的改變只能通過(guò) Action,不能通過(guò)其他方式。也就是說(shuō) Store 不應(yīng)該有公開(kāi)的 Setter,所有 Setter 都應(yīng)該是私有的,只能有公開(kāi)的 Getter。具體 Action 的處理邏輯一般放在 Store 里。
聽(tīng)聽(tīng)描述看看圖,可以發(fā)現(xiàn),F(xiàn)lux的最大特點(diǎn)就是數(shù)據(jù)都是單向流動(dòng)的。
ReduxFlux 有一些缺點(diǎn)(特點(diǎn)),比如一個(gè)應(yīng)用可以擁有多個(gè) Store,多個(gè)Store之間可能有依賴(lài)關(guān)系;Store 封裝了數(shù)據(jù)還有處理數(shù)據(jù)的邏輯。
所以大家在使用的時(shí)候,一般會(huì)用 Redux,他和 Flux 思想比較類(lèi)似,也有差別。
StoreRedux 里面只有一個(gè) Store,整個(gè)應(yīng)用的數(shù)據(jù)都在這個(gè)大 Store 里面。Store 的 State 不能直接修改,每次只能返回一個(gè)新的 State。Redux 整了一個(gè) createStore 函數(shù)來(lái)生成 Store。
import { createStore } from "redux"; const store = createStore(fn);
Store 允許使用 ?store.subscribe ?方法設(shè)置監(jiān)聽(tīng)函數(shù),一旦 State 發(fā)生變化,就自動(dòng)執(zhí)行這個(gè)函數(shù)。這樣不管 View 是用什么實(shí)現(xiàn)的,只要把 View 的更新函數(shù) subscribe 一下,就可以實(shí)現(xiàn) State 變化之后,View 自動(dòng)渲染了。比如在 React 里,把組件的render方法或setState方法訂閱進(jìn)去就行。
Action和 Flux ?一樣,Redux 里面也有 Action,Action 就是 View 發(fā)出的通知,告訴 Store State 要改變。Action 必須有一個(gè) type 屬性,代表 Action 的名稱(chēng),其他可以設(shè)置一堆屬性,作為參數(shù)供 State 變更時(shí)參考。
const action = { type: "ADD_TODO", payload: "Learn Redux" };
Redux 可以用 Action Creator 批量來(lái)生成一些 Action。
ReducerRedux 沒(méi)有 Dispatcher 的概念,Store 里面已經(jīng)集成了 dispatch 方法。store.dispatch()是 View 發(fā)出 Action 的唯一方法。
import { createStore } from "redux"; const store = createStore(fn); store.dispatch({ type: "ADD_TODO", payload: "Learn Redux" });
Redux 用一個(gè)叫做 Reducer 的純函數(shù)來(lái)處理事件。Store 收到 Action 以后,必須給出一個(gè)新的 State(就是剛才說(shuō)的Store 的 State 不能直接修改,每次只能返回一個(gè)新的 State),這樣 View 才會(huì)發(fā)生變化。這種 State 的計(jì)算過(guò)程就叫做 Reducer。
什么是純函數(shù)呢,就是說(shuō)沒(méi)有任何的副作用,比如這樣一個(gè)函數(shù):
function getAge(user) { user.age = user.age + 1; return user.age; }
這個(gè)函數(shù)就有副作用,每一次相同的輸入,都可能導(dǎo)致不同的輸出,而且還會(huì)影響輸入 user 的值,再比如:
let b = 10; function compare(a) { return a >= b; }
這個(gè)函數(shù)也有副作用,就是依賴(lài)外部的環(huán)境,b 在別處被改變了,返回值對(duì)于相同的 a 就有可能不一樣。
而 Reducer 是一個(gè)純函數(shù),對(duì)于相同的輸入,永遠(yuǎn)都只會(huì)有相同的輸出,不會(huì)影響外部的變量,也不會(huì)被外部變量影響,不得改寫(xiě)參數(shù)。它的作用大概就是這樣,根據(jù)應(yīng)用的狀態(tài)和當(dāng)前的 action 推導(dǎo)出新的 state:
(previousState, action) => newState
類(lèi)比 Flux,F(xiàn)lux 有些像:
(state, action) => state
為什么叫做 Reducer 呢?reduce ?是一個(gè)函數(shù)式編程的概念,經(jīng)常和 ?map ?放在一起說(shuō),簡(jiǎn)單來(lái)說(shuō),map ?就是映射,reduce ?就是歸納。映射就是把一個(gè)列表按照一定規(guī)則映射成另一個(gè)列表,而 reduce 是把一個(gè)列表通過(guò)一定規(guī)則進(jìn)行合并,也可以理解為對(duì)初始值進(jìn)行一系列的操作,返回一個(gè)新的值。
比如 ?Array 就有一個(gè)方法叫 reduce,Array.prototype.reduce(reducer, ?initialValue),把 Array 整吧整吧弄成一個(gè) ?newValue。
const array1 = [1, 2, 3, 4]; const reducer = (accumulator, currentValue) => accumulator + currentValue; // 1 + 2 + 3 + 4 console.log(array1.reduce(reducer)); // expected output: 10 // 5 + 1 + 2 + 3 + 4 console.log(array1.reduce(reducer, 5)); // expected output: 15
看起來(lái)和 Redux 的 Reducer 是不是好像好像,Redux 的 Reducer 就是 reduce 一個(gè)列表(action的列表)和一個(gè) initialValue(初始的 ?State)到一個(gè)新的 value(新的 ?State)。
把上面的概念連起來(lái),舉個(gè)例子:
下面的代碼聲明了 reducer:
const defaultState = 0; const reducer = (state = defaultState, action) => { switch (action.type) { case "ADD": return state + action.payload; default: return state; } };
createStore接受 Reducer 作為參數(shù),生成一個(gè)新的 Store。以后每當(dāng)store.dispatch發(fā)送過(guò)來(lái)一個(gè)新的 Action,就會(huì)自動(dòng)調(diào)用 Reducer,得到新的 State。
import { createStore } from "redux"; const store = createStore(reducer);
createStore?內(nèi)部干了什么事兒呢?通過(guò)一個(gè)簡(jiǎn)單的 createStore 的實(shí)現(xiàn),可以了解大概的原理(可以略過(guò)不看):
const createStore = (reducer) => { let state; let listeners = []; const getState = () => state; const dispatch = (action) => { state = reducer(state, action); listeners.forEach(listener => listener()); }; const subscribe = (listener) => { listeners.push(listener); return () => { listeners = listeners.filter(l => l !== listener); } }; dispatch({}); return { getState, dispatch, subscribe }; };
Redux 有很多的 Reducer,對(duì)于大型應(yīng)用來(lái)說(shuō),State 必然十分龐大,導(dǎo)致 Reducer 函數(shù)也十分龐大,所以需要做拆分。Redux ?里每一個(gè) Reducer 負(fù)責(zé)維護(hù) State 樹(shù)里面的一部分?jǐn)?shù)據(jù),多個(gè) Reducer 可以通過(guò) combineReducers 方法合成一個(gè)根 Reducer,這個(gè)根 Reducer 負(fù)責(zé)維護(hù)整個(gè) State。
import { combineReducers } from "redux"; // 注意這種簡(jiǎn)寫(xiě)形式,State 的屬性名必須與子 Reducer 同名 const chatReducer = combineReducers({ Reducer1, Reducer2, Reducer3 })
combineReducers 干了什么事兒呢?通過(guò)簡(jiǎn)單的 combineReducers 的實(shí)現(xiàn),可以了解大概的原理(可以略過(guò)不看):
const combineReducers = reducers => { return (state = {}, action) => { return Object.keys(reducers).reduce( (nextState, key) => { nextState[key] = reducers[key](state[key], action); return nextState; }, {} ); }; };流程
再回顧一下剛才的流程圖,嘗試走一遍 ?Redux ?流程:
1、用戶(hù)通過(guò) View 發(fā)出 Action:
store.dispatch(action);
2、然后 Store 自動(dòng)調(diào)用 Reducer,并且傳入兩個(gè)參數(shù):當(dāng)前 State 和收到的 Action。 Reducer 會(huì)返回新的 State 。
let nextState = xxxReducer(previousState, action);
3、State 一旦有變化,Store 就會(huì)調(diào)用監(jiān)聽(tīng)函數(shù)。
store.subscribe(listener);
4、listener可以通過(guò) ?store.getState() ?得到當(dāng)前狀態(tài)。如果使用的是 React,這時(shí)可以觸發(fā)重新渲染 View。
function listerner() { let newState = store.getState(); component.setState(newState); }對(duì)比 Flux
和 ?Flux ?比較一下:Flux 中 Store 是各自為戰(zhàn)的,每個(gè) Store 只對(duì)對(duì)應(yīng)的 View 負(fù)責(zé),每次更新都只通知對(duì)應(yīng)的View:
Redux 中各子 Reducer 都是由根 Reducer 統(tǒng)一管理的,每個(gè)子 Reducer 的變化都要經(jīng)過(guò)根 Reducer 的整合:
簡(jiǎn)單來(lái)說(shuō),Redux有三大原則:
單一數(shù)據(jù)源:Flux 的數(shù)據(jù)源可以是多個(gè)。
State 是只讀的:Flux 的 State 可以隨便改。
使用純函數(shù)來(lái)執(zhí)行修改:Flux 執(zhí)行修改的不一定是純函數(shù)。
Redux 和 Flux 一樣都是單向數(shù)據(jù)流。
中間件剛才說(shuō)到的都是比較理想的同步狀態(tài)。在實(shí)際項(xiàng)目中,一般都會(huì)有同步和異步操作,所以 Flux、Redux 之類(lèi)的思想,最終都要落地到同步異步的處理中來(lái)。
在 ?Redux ?中,同步的表現(xiàn)就是:Action 發(fā)出以后,Reducer 立即算出 State。那么異步的表現(xiàn)就是:Action 發(fā)出以后,過(guò)一段時(shí)間再執(zhí)行 Reducer。
那怎么才能 Reducer 在異步操作結(jié)束后自動(dòng)執(zhí)行呢?Redux 引入了中間件 Middleware 的概念。
其實(shí)我們重新回顧一下剛才的流程,可以發(fā)現(xiàn)每一個(gè)步驟都很純粹,都不太適合加入異步的操作,比如 Reducer,純函數(shù),肯定不能承擔(dān)異步操作,那樣會(huì)被外部IO干擾。Action呢,就是一個(gè)純對(duì)象,放不了操作。那想來(lái)想去,只能在 View 里發(fā)送 Action 的時(shí)候,加上一些異步操作了。比如下面的代碼,給原來(lái)的 ?dispatch ?方法包裹了一層,加上了一些日志打印的功能:
let next = store.dispatch; store.dispatch = function dispatchAndLog(action) { console.log("dispatching", action); next(action); console.log("next state", store.getState()); }
既然能加日志打印,當(dāng)然也能加入異步操作。所以中間件簡(jiǎn)單來(lái)說(shuō),就是對(duì) store.dispatch 方法進(jìn)行一些改造的函數(shù)。不展開(kāi)說(shuō)了,所以如果想詳細(xì)了解中間件,可以點(diǎn)這里。
Redux 提供了一個(gè) applyMiddleware 方法來(lái)應(yīng)用中間件:
const store = createStore( reducer, applyMiddleware(thunk, promise, logger) );
這個(gè)方法主要就是把所有的中間件組成一個(gè)數(shù)組,依次執(zhí)行。也就是說(shuō),任何被發(fā)送到 store 的 action 現(xiàn)在都會(huì)經(jīng)過(guò)thunk,promise,logger 這幾個(gè)中間件了。
處理異步對(duì)于異步操作來(lái)說(shuō),有兩個(gè)非常關(guān)鍵的時(shí)刻:發(fā)起請(qǐng)求的時(shí)刻,和接收到響應(yīng)的時(shí)刻(可能成功,也可能失敗或者超時(shí)),這兩個(gè)時(shí)刻都可能會(huì)更改應(yīng)用的 state。一般是這樣一個(gè)過(guò)程:
請(qǐng)求開(kāi)始時(shí),dispatch ?一個(gè)請(qǐng)求開(kāi)始 Action,觸發(fā) State 更新為“正在請(qǐng)求”狀態(tài),View 重新渲染,比如展現(xiàn)個(gè)Loading啥的。
請(qǐng)求結(jié)束后,如果成功,dispatch ?一個(gè)請(qǐng)求成功 Action,隱藏掉 ?Loading,把新的數(shù)據(jù)更新到 ?State;如果失敗,dispatch ?一個(gè)請(qǐng)求失敗 Action,隱藏掉 ?Loading,給個(gè)失敗提示。
顯然,用 ?Redux ?處理異步,可以自己寫(xiě)中間件來(lái)處理,當(dāng)然大多數(shù)人會(huì)選擇一些現(xiàn)成的支持異步處理的中間件。比如 redux-thunk 或?redux-promise?。
Redux-thunkthunk 比較簡(jiǎn)單,沒(méi)有做太多的封裝,把大部分自主權(quán)交給了用戶(hù):
const createFetchDataAction = function(id) { return function(dispatch, getState) { // 開(kāi)始請(qǐng)求,dispatch 一個(gè) FETCH_DATA_START action dispatch({ type: FETCH_DATA_START, payload: id }) api.fetchData(id) .then(response => { ? ? ? ?// 請(qǐng)求成功,dispatch 一個(gè) FETCH_DATA_SUCCESS action dispatch({ type: FETCH_DATA_SUCCESS, payload: response }) }) .catch(error => { ? ? ? ? ? ? ? ?// 請(qǐng)求失敗,dispatch 一個(gè) FETCH_DATA_FAILED action ? dispatch({ type: FETCH_DATA_FAILED, payload: error }) }) } } //reducer const reducer = function(oldState, action) { switch(action.type) { case FETCH_DATA_START : // 處理 loading 等 case FETCH_DATA_SUCCESS : // 更新 store 等 case FETCH_DATA_FAILED : // 提示異常 } }
缺點(diǎn)就是用戶(hù)要寫(xiě)的代碼有點(diǎn)多,可以看到上面的代碼比較啰嗦,一個(gè)請(qǐng)求就要搞這么一套東西。
Redux-promiseredus-promise 和 redux-thunk 的思想類(lèi)似,只不過(guò)做了一些簡(jiǎn)化,成功失敗手動(dòng) dispatch 被封裝成自動(dòng)了:
const FETCH_DATA = "FETCH_DATA" //action creator const getData = function(id) { return { type: FETCH_DATA, payload: api.fetchData(id) // 直接將 promise 作為 payload } } //reducer const reducer = function(oldState, action) { switch(action.type) { case FETCH_DATA: if (action.status === "success") { // 更新 store 等處理 } else { // 提示異常 } } }
剛才的什么 then、catch 之類(lèi)的被中間件自行處理了,代碼簡(jiǎn)單不少,不過(guò)要處理 Loading 啥的,還需要寫(xiě)額外的代碼。
其實(shí)任何時(shí)候都是這樣:封裝少,自由度高,但是代碼就會(huì)變復(fù)雜;封裝多,代碼變簡(jiǎn)單了,但是自由度就會(huì)變差。redux-thunk 和 redux-promise 剛好就是代表這兩個(gè)面。
redux-thunk 和 redux-promise ?的具體使用就不介紹了,這里只聊一下大概的思路。大部分簡(jiǎn)單的異步業(yè)務(wù)場(chǎng)景,redux-thunk 或者 redux-promise 都可以滿(mǎn)足了。
上面說(shuō)的 Flux 和 Redux,和具體的前端框架沒(méi)有什么關(guān)系,只是思想和約定層面。下面就要和我們常用的 Vue 或 React 結(jié)合起來(lái)了:
VuexVuex 主要用于 Vue,和 Flux,Redux 的思想很類(lèi)似。
Store每一個(gè) Vuex 里面有一個(gè)全局的 Store,包含著應(yīng)用中的狀態(tài) State,這個(gè) State 只是需要在組件中共享的數(shù)據(jù),不用放所有的 State,沒(méi)必要。這個(gè) State 是單一的,和 Redux 類(lèi)似,所以,一個(gè)應(yīng)用僅會(huì)包含一個(gè) Store 實(shí)例。單一狀態(tài)樹(shù)的好處是能夠直接地定位任一特定的狀態(tài)片段,在調(diào)試的過(guò)程中也能輕易地取得整個(gè)當(dāng)前應(yīng)用狀態(tài)的快照。
Vuex通過(guò) store 選項(xiàng),把 state 注入到了整個(gè)應(yīng)用中,這樣子組件能通過(guò) this.$store 訪(fǎng)問(wèn)到 state 了。
const app = new Vue({ el: "#app", // 把 store 對(duì)象提供給 “store” 選項(xiàng),這可以把 store 的實(shí)例注入所有的子組件 store, components: { Counter }, template: `` })
const Counter = { template: `{{ count }}`, computed: { count () { return this.$store.state.count } } }
State 改變,View 就會(huì)跟著改變,這個(gè)改變利用的是 Vue 的響應(yīng)式機(jī)制。
Mutation顯而易見(jiàn),State 不能直接改,需要通過(guò)一個(gè)約定的方式,這個(gè)方式在 Vuex 里面叫做 mutation,更改 Vuex 的 store 中的狀態(tài)的唯一方法是提交 mutation。Vuex 中的 mutation 非常類(lèi)似于事件:每個(gè) mutation 都有一個(gè)字符串的 事件類(lèi)型 (type) 和 一個(gè) 回調(diào)函數(shù) (handler)。
const store = new Vuex.Store({ state: { count: 1 }, mutations: { increment (state) { // 變更狀態(tài) state.count++ } } })
觸發(fā) mutation 事件的方式不是直接調(diào)用,比如 increment(state) ?是不行的,而要通過(guò) store.commit 方法:
store.commit("increment")
注意:mutation 都是同步事務(wù)。
mutation 有些類(lèi)似 Redux 的 Reducer,但是 Vuex 不要求每次都搞一個(gè)新的 State,可以直接修改 State,這塊兒又和 Flux 有些類(lèi)似。具尤大的說(shuō)法,Redux 強(qiáng)制的 immutability,在保證了每一次狀態(tài)變化都能追蹤的情況下強(qiáng)制的 immutability 帶來(lái)的收益很有限,為了同構(gòu)而設(shè)計(jì)的 API 很繁瑣,必須依賴(lài)第三方庫(kù)才能相對(duì)高效率地獲得狀態(tài)樹(shù)的局部狀態(tài),這些都是 Redux 不足的地方,所以也被 Vuex 舍掉了。
到這里,其實(shí)可以感覺(jué)到 Flux、Redux、Vuex 三個(gè)的思想都差不多,在具體細(xì)節(jié)上有一些差異,總的來(lái)說(shuō)都是讓 View 通過(guò)某種方式觸發(fā) Store 的事件或方法,Store 的事件或方法對(duì) State 進(jìn)行修改或返回一個(gè)新的 State,State 改變之后,View 發(fā)生響應(yīng)式改變。
Action到這里又該處理異步這塊兒了。mutation 是必須同步的,這個(gè)很好理解,和之前的 ?reducer 類(lèi)似,不同步修改的話(huà),會(huì)很難調(diào)試,不知道改變什么時(shí)候發(fā)生,也很難確定先后順序,A、B兩個(gè) mutation,調(diào)用順序可能是 A -> B,但是最終改變 State 的結(jié)果可能是 B -> A。
對(duì)比Redux的中間件,Vuex 加入了 Action 這個(gè)東西來(lái)處理異步,Vuex的想法是把同步和異步拆分開(kāi),異步操作想咋搞咋搞,但是不要干擾了同步操作。View 通過(guò) store.dispatch("increment") 來(lái)觸發(fā)某個(gè) Action,Action 里面不管執(zhí)行多少異步操作,完事之后都通過(guò) store.commit("increment") 來(lái)觸發(fā) mutation,一個(gè) Action 里面可以觸發(fā)多個(gè) mutation。所以 Vuex 的Action 類(lèi)似于一個(gè)靈活好用的中間件。
Vuex 把同步和異步操作通過(guò) mutation 和 Action 來(lái)分開(kāi)處理,是一種方式。但不代表是唯一的方式,還有很多方式,比如就不用 Action,而是在應(yīng)用內(nèi)部調(diào)用異步請(qǐng)求,請(qǐng)求完畢直接 commit mutation,當(dāng)然也可以。
Vuex 還引入了 Getter,這個(gè)可有可無(wú),只不過(guò)是方便計(jì)算屬性的復(fù)用。
Vuex 單一狀態(tài)樹(shù)并不影響模塊化,把 State 拆了,最后組合在一起就行。Vuex 引入了 Module 的概念,每個(gè) Module 有自己的 state、mutation、action、getter,其實(shí)就是把一個(gè)大的 Store 拆開(kāi)。
總的來(lái)看,Vuex 的方式比較清晰,適合 Vue 的思想,在實(shí)際開(kāi)發(fā)中也比較方便。
對(duì)比ReduxRedux:
view——>actions——>reducer——>state變化——>view變化(同步異步一樣)
Vuex:
view——>commit——>mutations——>state變化——>view變化(同步操作)
view——>dispatch——>actions——>mutations——>state變化——>view變化(異步操作)
Redux 和 Flux 類(lèi)似,只是一種思想或者規(guī)范,它和 React 之間沒(méi)有關(guān)系。Redux 支持 React、Angular、Ember、jQuery 甚至純 JavaScript。
但是因?yàn)?React 包含函數(shù)式的思想,也是單向數(shù)據(jù)流,和 Redux 很搭,所以一般都用 ?Redux 來(lái)進(jìn)行狀態(tài)管理。為了簡(jiǎn)單處理 ?Redux ?和 React ?UI ?的綁定,一般通過(guò)一個(gè)叫 react-redux 的庫(kù)和 React 配合使用,這個(gè)是 ?react ?官方出的(如果不用 react-redux,那么手動(dòng)處理 Redux 和 UI 的綁定,需要寫(xiě)很多重復(fù)的代碼,很容易出錯(cuò),而且有很多 UI 渲染邏輯的優(yōu)化不一定能處理好)。
Redux將React組件分為容器型組件和展示型組件,容器型組件一般通過(guò)connect函數(shù)生成,它訂閱了全局狀態(tài)的變化,通過(guò)mapStateToProps函數(shù),可以對(duì)全局狀態(tài)進(jìn)行過(guò)濾,而展示型組件不直接從global state獲取數(shù)據(jù),其數(shù)據(jù)來(lái)源于父組件。
如果一個(gè)組件既需要UI呈現(xiàn),又需要業(yè)務(wù)邏輯處理,那就得拆,拆成一個(gè)容器組件包著一個(gè)展示組件。
因?yàn)?react-redux 只是 redux 和 react 結(jié)合的一種實(shí)現(xiàn),除了剛才說(shuō)的組件拆分,并沒(méi)有什么新奇的東西,所以只拿一個(gè)簡(jiǎn)單TODO項(xiàng)目的部分代碼來(lái)舉例:
入口文件 index.js,把 redux 的相關(guān) store、reducer 通過(guò) Provider 注冊(cè)到 App 里面,這樣子組件就可以拿到 ?store ?了。
import React from "react" import { render } from "react-dom" import { Provider } from "react-redux" import { createStore } from "redux" import rootReducer from "./reducers" import App from "./components/App" const store = createStore(rootReducer) render(, document.getElementById("root") )
actions/index.js,創(chuàng)建 Action:
let nextTodoId = 0 export const addTodo = text => ({ type: "ADD_TODO", id: nextTodoId++, text }) export const setVisibilityFilter = filter => ({ type: "SET_VISIBILITY_FILTER", filter }) export const toggleTodo = id => ({ type: "TOGGLE_TODO", id }) export const VisibilityFilters = { SHOW_ALL: "SHOW_ALL", SHOW_COMPLETED: "SHOW_COMPLETED", SHOW_ACTIVE: "SHOW_ACTIVE" }
reducers/todos.js,創(chuàng)建 Reducers:
const todos = (state = [], action) => { switch (action.type) { case "ADD_TODO": return [ ...state, { id: action.id, text: action.text, completed: false } ] case "TOGGLE_TODO": return state.map(todo => todo.id === action.id ? { ...todo, completed: !todo.completed } : todo ) default: return state } } export default todos
reducers/index.js,把所有的 Reducers 綁定到一起:
import { combineReducers } from "redux" import todos from "./todos" import visibilityFilter from "./visibilityFilter" export default combineReducers({ todos, visibilityFilter, ... })
containers/VisibleTodoList.js,容器組件,connect 負(fù)責(zé)連接React組件和Redux Store:
import { connect } from "react-redux" import { toggleTodo } from "../actions" import TodoList from "../components/TodoList" const getVisibleTodos = (todos, filter) => { switch (filter) { case "SHOW_COMPLETED": return todos.filter(t => t.completed) case "SHOW_ACTIVE": return todos.filter(t => !t.completed) case "SHOW_ALL": default: return todos } } // mapStateToProps 函數(shù)指定如何把當(dāng)前 Redux store state 映射到展示組件的 props 中 const mapStateToProps = state => ({ todos: getVisibleTodos(state.todos, state.visibilityFilter) }) // mapDispatchToProps 方法接收 dispatch() 方法并返回期望注入到展示組件的 props 中的回調(diào)方法。 const mapDispatchToProps = dispatch => ({ toggleTodo: id => dispatch(toggleTodo(id)) }) export default connect( mapStateToProps, mapDispatchToProps )(TodoList)
簡(jiǎn)單來(lái)說(shuō),react-redux 就是多了個(gè) connect 方法連接容器組件和UI組件,這里的“連接”就是一種映射:
mapStateToProps ?把容器組件的 state 映射到UI組件的 props
mapDispatchToProps 把UI組件的事件映射到 dispatch 方法
Redux-saga剛才介紹了兩個(gè)Redux 處理異步的中間件 redux-thunk 和 redux-promise,當(dāng)然 redux 的異步中間件還有很多,他們可以處理大部分場(chǎng)景,這些中間件的思想基本上都是把異步請(qǐng)求部分放在了 ?action ?creator ?中,理解起來(lái)比較簡(jiǎn)單。
redux-saga 采用了另外一種思路,它沒(méi)有把異步操作放在 action creator 中,也沒(méi)有去處理 reductor,而是把所有的異步操作看成“線(xiàn)程”,可以通過(guò)普通的action去觸發(fā)它,當(dāng)操作完成時(shí)也會(huì)觸發(fā)action作為輸出。saga 的意思本來(lái)就是一連串的事件。
redux-saga 把異步獲取數(shù)據(jù)這類(lèi)的操作都叫做副作用(Side ?Effect),它的目標(biāo)就是把這些副作用管理好,讓他們執(zhí)行更高效,測(cè)試更簡(jiǎn)單,在處理故障時(shí)更容易。
在聊 redux-saga 之前,需要熟悉一些預(yù)備知識(shí),那就是 ES6 的 Generator。
如果從沒(méi)接觸過(guò) Generator 的話(huà),看著下面的代碼,給你個(gè)1分鐘傻瓜式速成,函數(shù)加個(gè)星號(hào)就是 Generator?函數(shù)了,Generator 就是個(gè)罵街生成器,Generator 函數(shù)里可以寫(xiě)一堆 yield 關(guān)鍵字,可以記成“丫的”,Generator 函數(shù)執(zhí)行的時(shí)候,啥都不干,就等著調(diào)用 next 方法,按照順序把標(biāo)記為“丫的”的地方一個(gè)一個(gè)拎出來(lái)罵(遍歷執(zhí)行),罵到最后沒(méi)有“丫的”標(biāo)記了,就返回最后的return值,然后標(biāo)記為 done: true,也就是罵完了(上面只是幫助初學(xué)者記憶,別噴~)。
function* helloWorldGenerator() { yield "hello"; yield "world"; return "ending"; } var hw = helloWorldGenerator(); hw.next() // 先把 "hello" 拎出來(lái),done: false 代表還沒(méi)罵完 // { value: "hello", done: false } next() 方法有固定的格式,value 是返回值,done 代表是否遍歷結(jié)束 hw.next() // 再把 "world" 拎出來(lái),done: false 代表還沒(méi)罵完 // { value: "world", done: false } hw.next() // 沒(méi)有 yield 了,就把最后的 return "ending" 拎出來(lái),done: true 代表罵完了 // { value: "ending", done: true } hw.next() // 沒(méi)有 yield,也沒(méi)有 return 了,真的罵完了,只能擠出來(lái)一個(gè) undefined 了,done: true 代表罵完了 // { value: undefined, done: true }
這樣搞有啥好處呢?我們發(fā)現(xiàn) Generator?函數(shù)的很多代碼可以被延緩執(zhí)行,也就是具備了暫停和記憶的功能:遇到y(tǒng)ield表達(dá)式,就暫停執(zhí)行后面的操作,并將緊跟在yield后面的那個(gè)表達(dá)式的值,作為返回的對(duì)象的value屬性值,等著下一次調(diào)用next方法時(shí),再繼續(xù)往下執(zhí)行。用 Generator 來(lái)寫(xiě)異步代碼,大概長(zhǎng)這樣:
function* gen(){ var url = "https://api.github.com/users/github"; var jsonData = yield fetch(url); console.log(jsonData); } var g = gen(); var result = g.next(); // 這里的result是 { value: fetch("https://api.github.com/users/github"), done: true } // fetch(url) 是一個(gè) Promise,所以需要 then 來(lái)執(zhí)行下一步 result.value.then(function(data){ return data.json(); }).then(function(data){ // 獲取到 json data,然后作為參數(shù)調(diào)用 next,相當(dāng)于把 data 傳給了 jsonData,然后執(zhí)行 console.log(jsonData); g.next(data); });
再回到 redux-saga 來(lái),可以把 saga 想象成開(kāi)了一個(gè)以最快速度不斷地調(diào)用 next 方法并嘗試獲取所有 yield 表達(dá)式值的線(xiàn)程。舉個(gè)例子:
// saga.js import { take, put } from "redux-saga/effects" function* mySaga(){ // 阻塞: take方法就是等待 USER_INTERACTED_WITH_UI_ACTION 這個(gè) action 執(zhí)行 yield take(USER_INTERACTED_WITH_UI_ACTION); // 阻塞: put方法將同步發(fā)起一個(gè) action yield put(SHOW_LOADING_ACTION, {isLoading: true}); // 阻塞: 將等待 FetchFn 結(jié)束,等待返回的 Promise const data = yield call(FetchFn, "https://my.server.com/getdata"); // 阻塞: 將同步發(fā)起 action (使用剛才返回的 Promise.then) yield put(SHOW_DATA_ACTION, {data: data}); }
這里用了好幾個(gè)yield,簡(jiǎn)單理解,也就是每個(gè) yield 都發(fā)起了阻塞,saga 會(huì)等待執(zhí)行結(jié)果返回,再執(zhí)行下一指令。也就是相當(dāng)于take、put、call、put 這幾個(gè)方法的調(diào)用變成了同步的,上面的全部完成返回了,才會(huì)執(zhí)行下面的,類(lèi)似于 await。
用了 saga,我們就可以很細(xì)粒度的控制各個(gè)副作用每一部的操作,可以把異步操作和同步發(fā)起 action 一起,隨便的排列組合。saga 還提供 takeEvery、takeLatest 之類(lèi)的輔助函數(shù),來(lái)控制是否允許多個(gè)異步請(qǐng)求同時(shí)執(zhí)行,尤其是 takeLatest,方便處理由于網(wǎng)絡(luò)延遲造成的多次請(qǐng)求數(shù)據(jù)沖突或混亂的問(wèn)題。
saga 看起來(lái)很復(fù)雜,主要原因可能是因?yàn)榇蠹也皇煜?Generator 的語(yǔ)法,還有需要學(xué)習(xí)一堆新增的 API 。如果拋開(kāi)這些記憶的東西,改造一下,再來(lái)看一下代碼:
function mySaga(){ if (action.type === "USER_INTERACTED_WITH_UI_ACTION") { store.dispatch({ type: "SHOW_LOADING_ACTION", isLoading: true}); const data = await Fetch("https://my.server.com/getdata"); store.dispatch({ type: "SHOW_DATA_ACTION", data: data}); ? ?} }
上面的代碼就很清晰了吧,全部都是同步的寫(xiě)法,無(wú)比順暢,當(dāng)然直接這樣寫(xiě)是不支持的,所以那些 Generator 語(yǔ)法和API,無(wú)非就是做一些適配而已。
saga 還能很方便的并行執(zhí)行異步任務(wù),或者讓兩個(gè)異步任務(wù)競(jìng)爭(zhēng):
// 并行執(zhí)行,并等待所有的結(jié)果,類(lèi)似 Promise.all 的行為 const [users, repos] = yield [ call(fetch, "/users"), call(fetch, "/repos") ] // 并行執(zhí)行,哪個(gè)先完成返回哪個(gè),剩下的就取消掉了 const {posts, timeout} = yield race({ ?posts: call(fetchApi, "/posts"), ?timeout: call(delay, 1000) })
saga 的每一步都可以做一些斷言(assert)之類(lèi)的,所以非常方便測(cè)試。而且很容易測(cè)試到不同的分支。
這里不討論更多 saga 的細(xì)節(jié),大家了解 saga 的思想就行,細(xì)節(jié)請(qǐng)看文檔。
對(duì)比 Redux-thunk比較一下 redux-thunk 和 redux-saga 的代碼:
和 redux-thunk 等其他異步中間件對(duì)比來(lái)說(shuō),redux-saga 主要有下面幾個(gè)特點(diǎn):
異步數(shù)據(jù)獲取的相關(guān)業(yè)務(wù)邏輯放在了多帶帶的 saga.js 中,不再是摻雜在 action.js 或 component.js 中。
dispatch 的參數(shù)是標(biāo)準(zhǔn)的 ?action,沒(méi)有魔法。
saga 代碼采用類(lèi)似同步的方式書(shū)寫(xiě),代碼變得更易讀。
代碼異常/請(qǐng)求失敗 都可以直接通過(guò) try/catch 語(yǔ)法直接捕獲處理。
很容易測(cè)試,如果是 thunk 的 Promise,測(cè)試的話(huà)就需要不停的 mock 不同的數(shù)據(jù)。
其實(shí) redux-saga 是用一些學(xué)習(xí)的復(fù)雜度,換來(lái)了代碼的高可維護(hù)性,還是很值得在項(xiàng)目中使用的。
DvaDva是什么呢?官方的定義是:dva 首先是一個(gè)基于 redux 和 redux-saga 的數(shù)據(jù)流方案,然后為了簡(jiǎn)化開(kāi)發(fā)體驗(yàn),dva 還額外內(nèi)置了 react-router 和 fetch,所以也可以理解為一個(gè)輕量級(jí)的應(yīng)用框架。
簡(jiǎn)單理解,就是讓使用 react-redux 和 redux-saga 編寫(xiě)的代碼組織起來(lái)更合理,維護(hù)起來(lái)更方便。
之前我們聊了 redux、react-redux、redux-saga 之類(lèi)的概念,大家肯定覺(jué)得頭昏腦漲的,什么 action、reducer、saga 之類(lèi)的,寫(xiě)一個(gè)功能要在這些js文件里面不停的切換。
dva 做的事情很簡(jiǎn)單,就是讓這些東西可以寫(xiě)到一起,不用分開(kāi)來(lái)寫(xiě)了。比如:
app.model({ // namespace - 對(duì)應(yīng) reducer 在 combine 到 rootReducer 時(shí)的 key 值 namespace: "products", // state - 對(duì)應(yīng) reducer 的 initialState state: { list: [], loading: false, }, // subscription - 在 dom ready 后執(zhí)行 subscriptions: [ function(dispatch) { dispatch({type: "products/query"}); }, ], // effects - 對(duì)應(yīng) saga,并簡(jiǎn)化了使用 effects: { ["products/query"]: function*() { yield call(delay(800)); yield put({ type: "products/query/success", payload: ["ant-tool", "roof"], }); }, }, // reducers - 就是傳統(tǒng)的 reducers reducers: { ["products/query"](state) { return { ...state, loading: true, }; }, ["products/query/success"](state, { payload }) { return { ...state, loading: false, list: payload }; }, }, });
以前書(shū)寫(xiě)的方式是創(chuàng)建 ?sagas/products.js, reducers/products.js 和 actions/products.js,然后把 saga、action、reducer 啥的分開(kāi)來(lái)寫(xiě),來(lái)回切換,現(xiàn)在寫(xiě)在一起就方便多了。
比如傳統(tǒng)的 TODO 應(yīng)用,用 redux + redux-saga 來(lái)表示結(jié)構(gòu),就是這樣:
saga 攔截 add 這個(gè) action, 發(fā)起 http 請(qǐng)求, 如果請(qǐng)求成功, 則繼續(xù)向 reducer 發(fā)一個(gè) addTodoSuccess 的 action, 提示創(chuàng)建成功, 反之則發(fā)送 addTodoFail 的 action 即可。
如果使用 Dva,那么結(jié)構(gòu)圖如下:
整個(gè)結(jié)構(gòu)變化不大,最主要的就是把 store 及 saga 統(tǒng)一為一個(gè) model 的概念(有點(diǎn)類(lèi)似 Vuex 的 Module),寫(xiě)在了一個(gè) js 文件里。增加了一個(gè) Subscriptions, 用于收集其他來(lái)源的 action,比如快捷鍵操作。
app.model({ namespace: "count", state: { record: 0, current: 0, }, reducers: { add(state) { const newCurrent = state.current + 1; return { ...state, record: newCurrent > state.record ? newCurrent : state.record, current: newCurrent, }; }, minus(state) { return { ...state, current: state.current - 1}; }, }, effects: { *add(action, { call, put }) { yield call(delay, 1000); yield put({ type: "minus" }); }, }, subscriptions: { keyboardWatcher({ dispatch }) { key("?+up, ctrl+up", () => { dispatch({type:"add"}) }); }, }, });
之前我們說(shuō)過(guò)約定優(yōu)于配置的思想,Dva正式借鑒了這個(gè)思想。
MobX前面扯了這么多,其實(shí)還都是 Flux 體系的,都是單向數(shù)據(jù)流方案。接下來(lái)要說(shuō)的 MobX,就和他們不太一樣了。
我們先清空一下大腦,回到初心,什么是初心?就是我們最初要解決的問(wèn)題是什么?最初我們其實(shí)為了解決應(yīng)用狀態(tài)管理的問(wèn)題,不管是 Redux 還是 MobX,把狀態(tài)管理好是前提。什么叫把狀態(tài)管理好,簡(jiǎn)單來(lái)說(shuō)就是:統(tǒng)一維護(hù)公共的應(yīng)用狀態(tài),以統(tǒng)一并且可控的方式更新?tīng)顟B(tài),狀態(tài)更新后,View跟著更新。不管是什么思想,達(dá)成這個(gè)目標(biāo)就ok。
Flux 體系的狀態(tài)管理方式,只是一個(gè)選項(xiàng),但并不代表是唯一的選項(xiàng)。MobX 就是另一個(gè)選項(xiàng)。
MobX背后的哲學(xué)很簡(jiǎn)單:任何源自應(yīng)用狀態(tài)的東西都應(yīng)該自動(dòng)地獲得。譯成人話(huà)就是狀態(tài)只要一變,其他用到狀態(tài)的地方就都跟著自動(dòng)變。
看這篇文章的人,大概率會(huì)對(duì)面向?qū)ο蟮乃枷氡容^熟悉,而對(duì)函數(shù)式編程的思想略陌生。Flux 或者說(shuō) Redux 的思想主要就是函數(shù)式編程(FP)的思想,所以學(xué)習(xí)起來(lái)會(huì)覺(jué)得累一些。而 MobX 更接近于面向?qū)ο缶幊?,它?state 包裝成可觀察的對(duì)象,這個(gè)對(duì)象會(huì)驅(qū)動(dòng)各種改變。什么是可觀察?就是 MobX 老大哥在看著 state 呢。state 只要一改變,所有用到它的地方就都跟著改變了。這樣整個(gè) View 可以被 state 來(lái)驅(qū)動(dòng)。
const obj = observable({ a: 1, b: 2 }) autoRun(() => { console.log(obj.a) }) obj.b = 3 // 什么都沒(méi)有發(fā)生 obj.a = 2 // observe 函數(shù)的回調(diào)觸發(fā)了,控制臺(tái)輸出:2
上面的obj,他的 obj.a 屬性被使用了,那么只要 obj.a 屬性一變,所有使用的地方都會(huì)被調(diào)用。autoRun 就是這個(gè)老大哥,他看著所有依賴(lài) obj.a 的地方,也就是收集所有對(duì) obj.a 的依賴(lài)。當(dāng) obj.a 改變時(shí),老大哥就會(huì)觸發(fā)所有依賴(lài)去更新。
MobX 允許有多個(gè) store,而且這些 store 里的 state 可以直接修改,不用像 Redux 那樣每次還返回個(gè)新的。這個(gè)有點(diǎn)像 Vuex,自由度更高,寫(xiě)的代碼更少。不過(guò)它也會(huì)讓代碼不好維護(hù)。
MobX 和 Flux、Redux 一樣,都是和具體的前端框架無(wú)關(guān)的,也就是說(shuō)可以用于 React(mobx-react) 或者 Vue(mobx-vue)。一般來(lái)說(shuō),用到 React 比較常見(jiàn),很少用于 Vue,因?yàn)?Vuex 本身就類(lèi)似 MobX,很靈活。如果我們把 MobX 用于 React ?或者 ?Vue,可以看到很多 setState() 和 this.state.xxx = 這樣的處理都可以省了。
還是和上面一樣,只介紹思想。具體 MobX 的使用,可以看這里。
對(duì)比 Redux我們直觀地上兩坨實(shí)現(xiàn)計(jì)數(shù)器代碼:
Redux:
import React, { Component } from "react"; import { createStore, bindActionCreators, } from "redux"; import { Provider, connect } from "react-redux"; // ①action types const COUNTER_ADD = "counter_add"; const COUNTER_DEC = "counter_dec"; const initialState = {a: 0}; // ②reducers function reducers(state = initialState, action) { switch (action.type) { case COUNTER_ADD: return {...state, a: state.a+1}; case COUNTER_DEC: return {...state, a: state.a-1}; default: return state } } // ③action creator const incA = () => ({ type: COUNTER_ADD }); const decA = () => ({ type: COUNTER_DEC }); const Actions = {incA, decA}; class Demo extends Component { render() { const { store, actions } = this.props; return (); } } // ④將state、actions 映射到組件 props const mapStateToProps = state => ({store: state}); const mapDispatchToProps = dispatch => ({ // ⑤bindActionCreators 簡(jiǎn)化 dispatch actions: bindActionCreators(Actions, dispatch) }) // ⑥connect產(chǎn)生容器組件 const Root = connect( mapStateToProps, mapDispatchToProps )(Demo) const store = createStore(reducers) export default class App extends Component { render() { return (a = {store.a}
) } }
MobX:
import React, { Component } from "react"; import { observable, action } from "mobx"; import { Provider, observer, inject } from "mobx-react"; // 定義數(shù)據(jù)結(jié)構(gòu) class Store { // ① 使用 observable decorator @observable a = 0; } // 定義對(duì)數(shù)據(jù)的操作 class Actions { constructor({store}) { this.store = store; } // ② 使用 action decorator @action incA = () => { this.store.a++; } @action decA = () => { this.store.a--; } } // ③實(shí)例化單一數(shù)據(jù)源 const store = new Store(); // ④實(shí)例化 actions,并且和 store 進(jìn)行關(guān)聯(lián) const actions = new Actions({store}); // inject 向業(yè)務(wù)組件注入 store,actions,和 Provider 配合使用 // ⑤ 使用 inject decorator 和 observer decorator @inject("store", "actions") @observer class Demo extends Component { render() { const { store, actions } = this.props; return (); } } class App extends Component { render() { // ⑥使用Provider 在被 inject 的子組件里,可以通過(guò) props.store props.actions 訪(fǎng)問(wèn) return (a = {store.a}
) } } export default App;
比較一下:
Redux 數(shù)據(jù)流流動(dòng)很自然,可以充分利用時(shí)間回溯的特征,增強(qiáng)業(yè)務(wù)的可預(yù)測(cè)性;MobX 沒(méi)有那么自然的數(shù)據(jù)流動(dòng),也沒(méi)有時(shí)間回溯的能力,但是 View 更新很精確,粒度控制很細(xì)。
Redux 通過(guò)引入一些中間件來(lái)處理副作用;MobX ?沒(méi)有中間件,副作用的處理比較自由,比如依靠 autorunAsync 之類(lèi)的方法。
Redux 的樣板代碼更多,看起來(lái)就像是我們要做頓飯,需要先買(mǎi)個(gè)調(diào)料盒裝調(diào)料,再買(mǎi)個(gè)架子放刀叉。。。做一大堆準(zhǔn)備工作,然后才開(kāi)始炒菜;而 MobX 基本沒(méi)啥多余代碼,直接硬來(lái),拿著炊具調(diào)料就開(kāi)干,搞出來(lái)為止。
但其實(shí)?Redux 和 MobX 并沒(méi)有孰優(yōu)孰劣,Redux 比 Mobx 更多的樣板代碼,是因?yàn)樘囟ǖ脑O(shè)計(jì)約束。如果項(xiàng)目比較小的話(huà),使用 MobX 會(huì)比較靈活,但是大型項(xiàng)目,像?MobX 這樣沒(méi)有約束,沒(méi)有最佳實(shí)踐的方式,會(huì)造成代碼很難維護(hù),各有利弊。一般來(lái)說(shuō),小項(xiàng)目建議 MobX 就夠了,大項(xiàng)目還是用 Redux 比較合適。
總結(jié)時(shí)光荏苒,歲月如梭。每一個(gè)框架或者庫(kù)只能陪你走一段路,最終都會(huì)逝去。留在你心中的,不是一條一條的語(yǔ)法規(guī)則,而是一個(gè)一個(gè)的思想,這些思想才是推動(dòng)進(jìn)步的源泉。
帥哥美女,如果你都看到這里了,那么不點(diǎn)個(gè)贊,你的良心過(guò)得去么?
參考鏈接https://cn.vuejs.org/v2/guide/state-management.html
https://vuex.vuejs.org/
https://cn.redux.js.org/docs/react-redux/
http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_two_async_operations.html
http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_three_react-redux.html
https://redux-saga-in-chinese.js.org
https://juejin.im/post/59e6cd68f265da43163c2821
https://react-redux.js.org/introduction/why-use-react-redux
https://segmentfault.com/a/1190000007248878
http://es6.ruanyifeng.com/#docs/generator
https://juejin.im/post/5ac1cb9d6fb9a028cf32a046
https://zhuanlan.zhihu.com/p/35437092
https://github.com/dvajs/dva/issues/1
https://cn.mobx.js.org
https://zhuanlan.zhihu.com/p/25585910
http://imweb.io/topic/59f4833db72024f03c7f49b4
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/100152.html
摘要:希望大家在這浮夸的前端圈里,保持冷靜,堅(jiān)持每天花分鐘來(lái)學(xué)習(xí)與思考。 今天的React題沒(méi)有太多的故事…… 半個(gè)月前出了248個(gè)Vue的知識(shí)點(diǎn),受到很多朋友的關(guān)注,都強(qiáng)烈要求再出多些React相前的面試題,受到大家的邀請(qǐng),我又找了20多個(gè)React的使用者,他們給出了328道React的面試題,由我整理好發(fā)給大家,同時(shí)發(fā)布在了前端面試每日3+1的React專(zhuān)題,希望對(duì)大家有所幫助,同時(shí)大...
摘要:要求通過(guò)要求數(shù)據(jù)變更函數(shù)使用裝飾或放在函數(shù)中,目的就是讓狀態(tài)的變更根據(jù)可預(yù)測(cè)性單向數(shù)據(jù)流。同一份數(shù)據(jù)需要響應(yīng)到多個(gè)視圖,且被多個(gè)視圖進(jìn)行變更需要維護(hù)全局狀態(tài),并在他們變動(dòng)時(shí)響應(yīng)到視圖數(shù)據(jù)流變得復(fù)雜,組件本身已經(jīng)無(wú)法駕馭。今天是 520,這是本系列最后一篇文章,主要涵蓋 React 狀態(tài)管理的相關(guān)方案。 前幾篇文章在掘金首發(fā)基本石沉大海, 沒(méi)什么閱讀量. 可能是文章篇幅太長(zhǎng)了?掘金值太低了? ...
摘要:我的前端知識(shí)清單做前端也有幾年了,整理了一份平時(shí)常用的技術(shù)列表,歡迎大家補(bǔ)充。 我的前端知識(shí)清單 做前端也有幾年了,整理了一份平時(shí)常用的技術(shù)列表,歡迎大家補(bǔ)充。 html方向 html4標(biāo)簽 html5標(biāo)簽,語(yǔ)義化,媒體標(biāo)簽等 svg canvas web workers manifest pwa:service worker(workbox) css方向 css2語(yǔ)法 css3...
摘要:我的前端知識(shí)清單做前端也有幾年了,整理了一份平時(shí)常用的技術(shù)列表,歡迎大家補(bǔ)充。 我的前端知識(shí)清單 做前端也有幾年了,整理了一份平時(shí)常用的技術(shù)列表,歡迎大家補(bǔ)充。 html方向 html4標(biāo)簽 html5標(biāo)簽,語(yǔ)義化,媒體標(biāo)簽等 svg canvas web workers manifest pwa:service worker(workbox) css方向 css2語(yǔ)法 css3...
閱讀 1639·2021-11-22 13:52
閱讀 1473·2021-09-29 09:34
閱讀 2848·2021-09-09 11:40
閱讀 3090·2019-08-30 15:54
閱讀 1322·2019-08-30 15:53
閱讀 1038·2019-08-30 11:01
閱讀 1440·2019-08-29 17:22
閱讀 2018·2019-08-26 10:57