redux深入理解之中間件(middleware)

本文代碼請看本人github,https://github.com/Rynxiao/redux-middleware

關於redux運用,請看之前一篇文章http://blog.csdn.net/yuzhongzi81/article/details/51880577

理解reduce函數

reduce() 方法接收一個函數作爲累加器(accumulator),數組中的每個值(從左到右)開始縮減,最終爲一個值。

arr.reduce([callback, initialValue])

關於reduce的用法,這裏不再做多述,可以去這裏查看

看如下例子:

let arr = [1, 2, 3, 4, 5];

// 10代表初始值,p代表每一次的累加值,在第一次爲10
// 如果不存在初始值,那麼p第一次值爲1
// 此時累加的結果爲15
let sum = arr.reduce((p, c) => p + c, 10);  // 25

// 轉成es5的寫法即爲:
var sum = arr.reduce(function(p, c) {
    console.log(p);
    return p + c;
}, 10);

下面我們再來看一個reduce的高級擴展。現在有這麼一個數據結構,如下:

let school = {
    name: 'Hope middle school',
    created: '2001',
    classes: [
        {
            name: '三年二班',
            teachers: [
                { name: '張二蛋', age: 26, sex: '男', actor: '班主任' },
                { name: '王小妞', age: 23, sex: '女', actor: '英語老師' }
            ]
        },
        {
            name: '明星班',
            teachers: [
                { name: '歐陽娜娜', age: 29, sex: '女', actor: '班主任' },
                { name: '李易峯', age: 28, sex: '男', actor: '體育老師' },
                { name: '楊冪', age: 111, sex: '女', actor: '藝術老師' }
            ]
        }
    ]
};

比如我想取到這個學校的第一個班級的第一個老師的名字,可能你會這樣寫:

school.classes[0].teachers[0].name

這樣不就行了麼?so easy!是哦,這樣寫”毫無問題”,這個毫無問題的前提是你已經知道了這個值確實存在,那麼如果你不知道呢?或許你要這麼寫:

school.classes &&
school.classes[0] &&
school.classes[0].teachers &&
school.classes[0].teachers[0] &&
school.classes[0].teachers[0].name

我去,好大一坨,不過要在深層的對象中取值的場景在工作中真真實實存在呀?怎麼辦?逛知乎逛到一個大神的解決方案,如下:

const get = (p, o) => p.reduce((xs, x) => (xs && xs[x] ? xs[x] : null), o);

// call
get('classes', 0, 'teachers', 0, 'name', school);   // 張二蛋

是不是很簡單,用reduce這個方法優雅地解決了這個問題。

理解redux的compose函數

講了這麼久的reduce,這不是講redux麼?這就尷尬了,下面我們就來看看爲什麼要講這個reduce函數。去github上找到redux源碼,會看到一個compose.js文件,帶上註釋共22行,其中就用到了reduce這個函數,那麼這個函數是用來做啥的?可以看一看:

export default function compose(...funcs) {
    if (funcs.length === 0) {
      return arg => arg
    }

    if (funcs.length === 1) {
      return funcs[0]
    }

    return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

初步看上去貌似就是函數的嵌套調用。我們去搜一下,看哪個地方會用到這個函數,在源碼中找一下,發現在applyMiddleware.js中發現了這樣的調用:

export default function applyMiddleware(...middlewares) {
    return (createStore) => (reducer, preloadedState, enhancer) => {
        const store = createStore(reducer, preloadedState, enhancer)
        let dispatch = store.dispatch
        let chain = []

        const middlewareAPI = {
          getState: store.getState,
          dispatch: (...args) => dispatch(...args)
        }
        chain = middlewares.map(middleware => middleware(middlewareAPI))
        dispatch = compose(...chain)(store.dispatch)

        return {
          ...store,
          dispatch
        }
    }
}

看到熟悉的東西了麼?applyMiddleware喲,我們在寫中間件必須要用的函數。我們來看一下一個簡單的middleware是怎樣寫的?比如我要寫一個loggerMiddleware,那麼就像這樣:

const logger = store => next => action => {
    console.log('action', action);
    let result = next(action);
    console.log('logger after atate', store.getState());
    return result;
}

當我們創建了一個store的時候,我們是這樣調用的:

let middlewares = [loggerMiddleware, thunkMiddleware, ...others];
let store = applyMiddleware(middlewares)(createStore)(reducer, initialState);

那麼傳給compose的funcs實際上就是包含這樣的函數的一個數組:

function(next) {
    return function(action) {
        return next(action);
    }
}

當把這樣的一個數組傳給compose會發生什麼樣的化學反應呢?稍微看一下應該不難看出,最終會返回一個函數,這個函數是通過了層層middleware的加工,最終的形態仍如上面的這個樣子。注意,此時的next(action)並未執行,當執行了

compose(...chain)(store.dispatch)

之後,返回的樣子是這樣的:

function(action) {
    return next(action);
}

各位看官們,看出了一點點什麼東西了麼?好像createStore中的dispatch呀,沒錯,這其實也是一個dispatch,只是這個dispatch正一觸即發,再等待一個機會。我們有這麼一個數量加1的action,類似這樣的:

export function addCount() {
    return {
        type : ADD_COUNT
    }
}

// 下面我們來觸發一下
dispatch(addCount());

沒錯,此時的dispatch執行啦,最外層的dispatch執行了會發生什麼樣的反應呢?看下面:

return next(action);

// 這個next就是dispatch函數,只不過這個dispatch函數在每次執行的時候,會保留
// 上一個middleware傳遞的dispatch函數的引用,因此會一直的傳遞下去,
// 直到最終的store.dispatch執行

那麼我們去createStore中去看看dispatch函數的定義:

function dispatch(action) {
      // ...

      try {
            isDispatching = true
            currentState = currentReducer(currentState, action)
      } finally {
            isDispatching = false
      }

      // ...

      return action
  }

找到這一句

currentState = currentReducer(currentState, action);

當執行了這一步的時候,這一刻,原本傳遞過來的initialState值已經改變了,那麼就會層層執行middleware之後的操作,還記得我們在middleware中這樣寫了麼:

const logger = store => next => action => {
    console.log('action', action);
    let result = next(action);
    console.log('logger after atate', store.getState());
    return result;
}

這就是爲什麼我們會在next執行之後,會取到store中的state的原因。

異步的middlewares

異步的action寫法上可能會和立即執行的action不一樣,例如是這樣的:

// 定義的非純函數,提供異步請求支持
// 需要在sotre中使用thunkMiddleware
export function refresh() {
    return dispatch => {
        dispatch(refreshStart());
        return fetch(`src/mock/fetch-data-mock.json`)
            .then(response => response.json())
            .then(json => {
                setTimeout(() => {
                    dispatch(refreshSuccess(json && json.data.list));
                }, 3000);
            });
    }
}

爲什麼要使用thunkMiddleware呢,我們去找一找thunkMiddleware中到底寫了什麼?

function createThunkMiddleware(extraArgument) {
    return ({ dispatch, getState }) => next => action => {
        if (typeof action === 'function') {
            return action(dispatch, getState, extraArgument);
        }

        return next(action);
    };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

短短14行代碼,看這一句:

if (typeof action === 'function') {
    return action(dispatch, getState, extraArgument);
}

如果action的類型爲function的話,那麼就直接執行啦,實際上就是將一個異步的操作轉化成了兩個立即執行的action,只是需要在異步前和異步後分別發送狀態。爲什麼要分解呢?如果不分解會是什麼樣的情況?還記得這一行代碼嗎?

currentReducer(currentState, action);

這裏的reducer是一個純函數,接受的action必須爲帶有type字段的一個對象。所以你傳個函數是個什麼鬼?那不是直接走switchdefault了麼?所以得到的state依舊是之前的state,沒有任何改變。

發佈了60 篇原創文章 · 獲贊 29 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章