Redux 高階 -- 原始碼分析

阿希發表於2018-04-25

createStore.js

createStore

redux 的一切都從 createStore 開始

const store = redux.createStore(reducer, preloadedState, enhancer);
複製程式碼

createStore 可以接受三個引數:

  • reducer 必須 {functioin} 計算 state 改變的函式
  • preloadState 可選 {object} 最初的 state
  • enhancer 可選 {function} store 的增強器

如果沒有設定最初的 state,第二個引數也可以傳 enhancer,也是會正確執行的。

const store = redux.createStore(reducer, enhancer);
複製程式碼

因為原始碼裡面有這麼一段:

// 如果第二個引數是 function,沒有第三個引數
// 那麼就認為第二個引數是 enhancer,preloadedState 是 undefined
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
  enhancer = preloadedState
  preloadedState = undefined
}
複製程式碼

最終 createStore 返回一個 store 物件:

return {
  dispatch,
  subscribe,
  getState,
  replaceReducer,
  // ...
}
複製程式碼

接下來我們一個個來看 store 物件裡的這些成員。

dispatch

dispatch 負責發出 action:

store.dispatch({ type: 'ACTION_TYPE' });
複製程式碼

dispatch 只接收一個引數,那就是 action,最終返回這個 action。

dispatch 只做了兩件事:

  • 獲取 reducer 計算的新的 state,並把舊的 state 換成新的 state
  • 呼叫通過 store.subscribe(listener) 註冊的 listener

原始碼:

function dispatch(action) {
  // ...
  if (isDispatching) {
    throw new Error('Reducers may not dispatch actions.')
  }

  try {
    isDispatching = true

    // **核心工作**:呼叫 reducer 計算出最新的 state
    // oldState + action => newState
    // reducer 在獲取到新的 action 通知後,計算出最新的 state
    currentState = currentReducer(currentState, action)
  } finally {
    isDispatching = false
  }

  // 呼叫通過 store.subscribe(listener) 註冊的監聽函式
  const listeners = (currentListeners = nextListeners)
  for (let i = 0; i < listeners.length; i++) {
    const listener = listeners[i]
    listener()
  }

  // 原封不動的返回 action
  return action
}
複製程式碼

reducer

從上面的原始碼:

currentState = currentReducer(currentState, action)
// nextState = reducer(prevState, action)
複製程式碼

可以看出,我們傳進去的引數 reducer 必須是這樣的:接受 state 和 action,通過一頓計算之後,返回新的 state。至於怎麼計算,就是我們自己編寫了。

subscribe

註冊事件,每次 dispatch action 的時候都會呼叫。

subscribe(listener) 接收一個 {function} listener 作為引數,同時返回一個函式用來登出原來註冊的時間 listener

function subscribe(listener) {
  //...
  nextListeners.push(listener)

  return function unsubscribe() {
    // ...
    const index = nextListeners.indexOf(listener)
    nextListeners.splice(index, 1)
  }
}

// 註冊
const unsubscribe = store.subscribe(() => { console.log('action dispatched!'); });

// 登出
unsubscribe();
複製程式碼

getState

getState 方法返回最新的 state

function getState() {
  // ...
  return currentState
}
複製程式碼

replaceReducer

replaceReducer 接收一個引數 {function} reducer。用傳入的 reducer 替換原來的 reducer

function replaceReducer(nextReducer) {
  // ...
  currentReducer = nextReducer
  dispatch({ type: ActionTypes.REPLACE })
}
複製程式碼

createStore 續

接下來我們繼續回來看看 createStore 裡的邏輯:

function createStore(reducer, preloadedState, enhancer) {
  // ...

  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, preloadedState)
  }

  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }

  let currentReducer = reducer
  let currentState = preloadedState
  let currentListeners = []
  let nextListeners = currentListeners
  let isDispatching = false
  // ...

  dispatch({ type: ActionTypes.INIT })

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }
}
複製程式碼

對的,就只有這麼一點邏輯:如果有 enhancer,返回 enhancer 的執行結果,然後 dispatch 一個初始化的 action -- { type: ActionTypes.INIT } 就沒了。

所以這個 enhancer 大有文章,是塊硬骨頭。而 enhancer 我們在使用的時候知道,是呼叫 applyMiddleware 生成的,但是 applyMiddleware 裡有個非常重要的函式,必須要先拿出來講一講 -- compose

compose.js

compose 函式接收若干個函式作為引數,並從右到左組合起來,返回這個組合起來的函式。

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)))
}
複製程式碼

舉個例子:

function a(...args) { console.log('a', [...args]); return { a: [...args] }; }
function b(...args) { console.log('b', [...args]); return { b: [...args] }; }
function c(...args) { console.log('c', [...args]); return { c: [...args] }; }
compose(a, b, c);
複製程式碼

大家可以思考下返回的是什麼

(...args) => a(b(c(...args)))
複製程式碼

對的,返回的是一個函式,這個函式接收若干個引數 args,並且把這些引數給 c 執行,然後 c 執行的結果再次作為引數給 b 執行,以此類推。讓我們來試一下吧:

compose(a, b, c)(1, 2, 3, 4, 5);
// c [1, 2, 3, 4, 5]
// b [{ c: [1, 2, 3, 4, 5] }]
// a [{ b: [{ c: [1, 2, 3, 4, 5]}] }]
複製程式碼

applyMiddleware.js

搞懂了 compose,接下來就可以看這個原始碼了:

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
    }
  }
}
複製程式碼

原來 applyMiddleware 啥也沒做,就返回了一個函式 createStore => { // ... }。這就是我們傳到 createStore 裡的 enhancer,它又接收 createStore 函式作為引數,返回了一個函式 (...args) => { // ... },我們暫且叫它 executor。

所以上面原始碼的這麼一段可以這樣拆分:

function createStore(reducer, preloadedState, enhancer) {
  // ...
  return enhancer(createStore)(reducer, preloadedState)
  // ...
}

// 拆分為
const enhancer = applyMiddleware(middileware1, middleware2);

const executor = enhancer(createStore);
const result = executor(reducer, preloadedState); // 拆分之後就可以主要看這一段了
return result;
複製程式碼

單獨拎出來看看

function executor(...args) {

  // 通過傳入的引數建立新的 store
  const store = createStore(...args)

  // 預設的 dispath
  let dispatch = () => {
    throw new Error(
      `Dispatching while constructing your middleware is not allowed. ` +
        `Other middleware would not be applied to this dispatch.`
    )
  }

  // 注入給 middleware() 的引數 { getState, dispatch }
  // 前面的文章有講到如何寫 middleware, 它的形式:
  // ({ getState, dispatch }) => next => action => { // ... }
  const middlewareAPI = {
    getState: store.getState,
    dispatch: (...args) => dispatch(...args)
  }

  // 把 middleware 執行並存放到陣列裡
  // chain = [ next => action => { // middleware1 }, next => action => { //middleware2 }]
  const chain = middlewares.map(middleware => middleware(middlewareAPI))

  // compose 起來,傳給最後一個 middleware 的引數是 store.dispatch,上面有講過 compose
  dispatch = compose(...chain)(store.dispatch)

  return {
    ...store,

    // 用新的 dispatch 替換最早的 store.dispatch
    dispatch
  }
}
複製程式碼

我們還是把上面的 dispatch = compose(...chain)(store.dispatch) 拆開來看:

compose(...chain)
// compose(next => action => { console.log('m1', next) }, next => action => { console.log('m2', next) });

// 結果
const executor = (...args) => a(b(...args));

//其中
// a = next => action => { console.log('m1', next) }
// b = next => action => { console.log('m2', next) }

// 然後
dispatch = executor(store.dispatch)

// 結果
dispatch = a(b(store.dispatch))

// 即
(store.dispatch /** m2 next */ => {
  action => {
    console.log('m2', next)
  }
} /** m1 next */ ) => {
  action => {
    console.log('m1', next);
  }
}
複製程式碼

總結下來,假設有 3 箇中介軟體:m1m2m3。這個時候 dispatch 了一個 action。此時的 dispatch 已經不是最原始的 store.dispatch 了。而是經過 compose 過中介軟體的 dispatch。這個 action 會依次經過 m1, m2, m3 的處理,m1 處理完了之後會呼叫 next(action),這個 next 就是 m2,實際上是呼叫第二個中介軟體處理 action,然後依次類推,m2 會呼叫 next(action),這個 next 就是 m3。因為 m3 是最後一箇中介軟體,所以 m3 裡的 next 實際上就是 store.dispatch

我們來用這個例子驗證一下:

const redux = require('redux');

const enhancer = redux.applyMiddleware(
  function({ dispatch }) {
    return function(next) {
      return function(action) {
        console.log('m1', next);
        next(action);
      }
    }
  },
  function({ dispatch }) {
    return function(next) {
      return function(action) {
        console.log('m2', next);
        next(action);
      }
    }
  },
  function({ dispatch }) {
    return function(next) {
      return function(action) {
        console.log('m3', next);
        next(action);
      }
    }
  }
);

const store = redux.createStore(function(state = { test: 'a' }, action) {
  if (action.type === 'TEST') {
    return Object.assign({}, state, { test: 'b' });
  }
  return state;
}, enhancer);

store.dispatch({ type: 'TEST' });
// m1 function (action) {
//         console.log('m2', next);
//         next(action);
//       }
// m2 function (action) {
//         console.log('m3', next);
//         next(action);
//       }
// m3 function dispatch(action) {
//     if (!(0, _isPlainObject2['default'])(action)) {
//       throw new Error('Actions must be plain objects. ' + 'Use custom middleware for async actions.');
//     }

//     if (typeof action.type === 'undefined') {
//       throw new Error('Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?');
//     }

//     if (isDispatching) {
//       throw new Error('Reducers may not dispatch actions.');
//     }

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

//     var listeners = currentListeners = nextListeners;
//     for (var i = 0; i < listeners.length; i++) {
//       var listener = listeners[i];
//       listener();
//     }

//     return action;
//   }
複製程式碼

結果和預料的是一樣的。

程式碼地址:Redux 高階 -- 原始碼分析,控制檯執行 node ./demo6/index.js 即可

上一篇:Redux 進階 -- 編寫和使用中介軟體

相關文章