成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專(zhuān)欄INFORMATION COLUMN

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

hiYoHoo / 3313人閱讀

摘要:也就是說(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)了。

Flux

Flux其實(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)的。

Redux

Flux 有一些缺點(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)似,也有差別。

Store

Redux 里面只有一個(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。

Reducer

Redux 沒(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-thunk

thunk 比較簡(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-promise

redus-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)了:

Vuex

Vuex 主要用于 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ì)比Redux

Redux:
view——>actions——>reducer——>state變化——>view變化(同步異步一樣)

Vuex:
view——>commit——>mutations——>state變化——>view變化(同步操作)
view——>dispatch——>actions——>mutations——>state變化——>view變化(異步操作)

React-redux

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)目中使用的。

Dva

Dva是什么呢?官方的定義是: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 (
      

a = {store.a}

); } } // ④將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 ( ) } }

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 (
      

a = {store.a}

); } } class App extends Component { render() { // ⑥使用Provider 在被 inject 的子組件里,可以通過(guò) props.store props.actions 訪(fǎng)問(wèn) return ( ) } } 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

相關(guān)文章

  • React 328道最全面試題(持續(xù)更新)

    摘要:希望大家在這浮夸的前端圈里,保持冷靜,堅(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í)大...

    kumfo 評(píng)論0 收藏0
  • React組件設(shè)計(jì)實(shí)踐總結(jié)05 - 狀態(tài)管理

    摘要:要求通過(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)了?掘金值太低了? ...

    ideaa 評(píng)論0 收藏0
  • 我的前端知識(shí)清單

    摘要:我的前端知識(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...

    kidsamong 評(píng)論0 收藏0
  • 我的前端知識(shí)清單

    摘要:我的前端知識(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...

    Alan 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<