1. 程式人生 > >redux原始碼閱讀之compose,applyMiddleware

redux原始碼閱讀之compose,applyMiddleware

我的觀點是,看別人的原始碼,不追求一定要能原樣造輪子,單純就是學習知識,對於程式設計師的提高就足夠了。在閱讀redux的compose原始碼之前,我們先學一些前置的知識。

redux原始碼閱讀之compose,applyMiddleware

看別人的原始碼就是學習知識。我們先學一些東西。

rest引數

形式為...變數名,用於獲取函式的多餘引數 ,該變數將多餘的引數放入陣列中, 只能是引數的最後一個。
function rest(...args){ console.log(args); }; rest('arg1','arg2')
那麼args變數就是一個數組['arg1','arg2']。

擴充套件運算子

擴充套件運算子也是三個點。好比rest引數的逆運算,將一個數組轉為用逗號分隔的引數序列

arr.reduce(callback[initialValue])

個人感覺reduce用法非常魔幻,要搞明白它之後,才能明白compose。
MDN的reduce文件:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce

使用

const array1 = [1, 2, 3, 4];
const reducer = (accumulator, currentValue) => accumulator + currentValue;

// 1 + 2 + 3 + 4
console.log(array1.reduce(reducer));
// 10

// 5 + 1 + 2 + 3 + 4
console.log(array1.reduce(reducer, 5));
// 15

array1.reduce((accumulator, currentValue) => accumulator + currentValue,5)

根據MDN的定義,
陣列的reduce() 方法對陣列中的每個元素執行一個由您提供的reducer函式(升序執行),將其結果彙總為單個返回值。

arr.reduce(callback[initialValue])

實際就是arr.reduce(callback, initialValue);

callback 函式接收4個引數:

Accumulator (acc) (累加器)
Current Value (cur) (當前值)
Current Index (idx) (當前索引)
Source Array (src) (源陣列)

initialValue 可選

作為第一次呼叫 callback函式時的第一個引數的值。 如果沒有提供初始值,則將使用陣列中的第一個元素。 不傳這個初始值initialVale,且在空陣列上呼叫 reduce 將報 Uncaught TypeError: Reduce of empty array with no initial value的錯誤。

pip

研究compose之前,我們再來看MDN上reduce的應用,一個功能型函式管道。https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce#%E5%8A%9F%E8%83%BD%E5%9E%8B%E5%87%BD%E6%95%B0%E7%AE%A1%E9%81%93

類似的我們來寫一個。

const pipe = (...fns) => 
  (args) => fns.reduce(
    (args, fn) => fn(args)
  ,args)

吐槽一下,就是這種es6寫法,完全不寫return,很多es5過來的人不習慣,所以很多原始碼很難看清楚。我們改寫一下,傳入引數字串‘test’,然後執行這個函式。就能明白pip函式的作用就是順序執行了addFont,toUpper。

args就是'test',...fns就是rest引數,pip函式裡的fns引數就是陣列[addfont,toUpper]。


const pipe = (...fns) => {
  return (args) => {
    return fns.reduce((args, fn) => {
      return fn(args)
    }, args)
  }
}

const toUpper = (value) => {
  return value.toUpperCase();
}

const addFont = (value) => {
  return 'hello plus!' + value;
}

console.log(pipe(addFont,toUpper)('test'));
// HELLO PLUS!TEST

pipe(addFont,toUpper)之後,返回一個函式.
pipe(addFont,toUpper)('test')形如

(args) => {
    return fns.reduce((args, fn) => {
      return fn(args)
    }, args)
  }('test');

再執行就是

fns.reduce(('test',fn)=>{
    return fn('test');
},'test')

這樣子看程式碼,這裡的reduce應該似乎比較好懂的。

compose

compose原始碼

/**
 * Composes single-argument functions from right to left. The rightmost
 * function can take multiple arguments as it provides the signature for
 * the resulting composite function.
 *
 * @param {...Function} funcs The functions to compose.
 * @returns {Function} A function obtained by composing the argument functions
 * from right to left. For example, compose(f, g, h) is identical to doing
 * (...args) => f(g(h(...args))).
 */

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)))
}

跟pip函式對比

console.log(pipe(addFont,toUpper)('test'));
// HELLO PLUS!TEST

console.log(compose(addFont,toUpper)('test'));
// hello plus!TEST

compose正好相反,後新增的方法先執行。然後寫的也很玄學讓人費解。讓我們改成pip函式一樣,也是同樣的效果,感覺這樣子就跟上面的pip一樣好理解一些了呢。

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

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

  return (...funcs) => (...args) => funcs.reduce((a, b)  => a(b(...args)))
}
console.log(compose(addFont,toUpper)('test'));
// hello plus!TEST

applyMiddleware

我們再來看redux引入中介軟體的用法,

const store = createStore(
  reducer,
  applyMiddleware(...middleware)
);

以及applyMiddleware方法原始碼

export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    const store = createStore(...args)
    let dispatch = () => {
      throw new Error(
        `Dispatching while constructing your middleware is not allowed. ` +
          `Other middleware would not be applied to this dispatch.`
      )
    }

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

    return {
      ...store,
      dispatch
    }
  }
}

實際上所謂redux的中介軟體就是改寫了store.dipatch方法。比如我們自己實現一個實現一個列印action的type的redux中介軟體,我們就可以這樣寫。

./redux-middlewares/log.js

const logger = ()=>{
    return ({dispatch,getState}) => next => action =>{
        console.log('dispatching', action);
        return next(action);
    }
};

export default logger;

跟別的redux中介軟體一起引入

import createSaga from 'redux-saga';
import createLog from './redux-middlewares/log';
const saga = createSaga(); 
const log = createLog();
const middlewares = [saga,log]
const store = createStore(
  rootReducer,
  applyMiddleware(...middlewares)
);
store.dispatch({type:'myAction'});

// 列印 dispatching {type: "myAction"}

這裡
redux的新增中介軟體
applyMiddleware(...middleware) 就等於這裡的
dispatch = compose(...chain)(store.dispatch).
跟我們實行
compose(addFont,toUpper)('test')是一樣的效果。

參考文章:

https://www.cnblogs.com/cloud-/p/7282188.html