1. 程式人生 > >Redux 原始碼解讀

Redux 原始碼解讀

Redux 的原始碼非常的精煉,短短几百行程式碼,確提供了強大的功能。今天,我們就來一探究竟。

看原始碼最簡單的方式,就是從入口檔案來看,看它依賴哪些模組,然後在依次看這些模組的內容,最後也就對整個程式碼有個清晰的認識了。

所以我們就從入口檔案開始來看:

import createStore from './createStore'
import combineReducers from './combineReducers'
import bindActionCreators from './bindActionCreators'
import applyMiddleware from './applyMiddleware'
import compose from './compose'
import warning from './utils/warning'

/*
* This is a dummy function to check if the function name has been altered by minification.
* If the function has been minified and NODE_ENV !== 'production', warn the user.
*/
function isCrushed() {}
// 就是根據 isCrushed 是否被壓縮了,來警告開發者正在非生產環境使用一個壓縮過的程式碼。
if (
  process.env.NODE_ENV !== 'production' &&
  typeof isCrushed.name === 'string' &&
  isCrushed.name !== 'isCrushed'
) {
  warning(
    'You are currently using minified code outside of NODE_ENV === \'production\'. ' +
    'This means that you are running a slower development build of Redux. ' +
    'You can use looseenvify (https://github.com/zertosh/looseenvify) for browserify ' +
    'or DefinePlugin for webpack (http://stackoverflow.com/questions/30030031) ' +
    'to ensure you have the correct code for your production build.'
  )
}

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose
}

可以看到它依賴了下面這幾個模組:

  • createStore
  • combineReducers
  • bindActionCreators
  • applyMiddleware
  • compose
  • warning

其他沒什麼說的,就是把一些 API 暴露出去。那我們就先按照這個模組依賴順序,依次進行解讀。

createStore

首先是createStore, 用來建立整個應用的 store .
它的依賴模組,都是些工具函式。

  • isPlainObject
  • $$observable
export default function createStore(reducer, preloadedState, enhancer) {
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

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

這裡邏輯很簡單:

第一個 if 語句的意思就是說,如果只傳入了兩個引數,且第二個引數 preloadedState 為函式,那麼就認為第二個引數為 enhancer .

第二個 if 語句確保 enhancer 是一個函式,並且當 enhancer 作為引數傳入的時候,返回 enhancer(createStore)(reucer, preloadedState) 作為 createStore 的返回,也就是我們要的 store.

第三個 if 語句確保 reducer 是一個函式。

接著往下看:

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

function ensureCanMutateNextListeners() {
  if (nextListeners === currentListeners) {
    nextListeners = currentListeners.slice()
  }
}

/**
  * Reads the state tree managed by the store.
  *
  * @returns {any} The current state tree of your application.
  */
function getState() {
  return currentState
}

這裡,把 preloadState 賦值給 currentState ,可以使應用直接重現某一個狀態,也可以用來做服務端渲染時直接由後臺計算出來作為應用的初始狀態。

ensureCanMutateNextListeners 這個函式在 nextListeners === currentListeners 成立時把 currentListeners 複製了一份賦值給了 nextListeners . 用來做什麼還不太清楚,先放著。

然後定義了一個獲取當前 state 的方法。

subscribe

接下來是一個subscribe 方法。

  /**
    * Adds a change listener. It will be called any time an action is dispatched,
    * and some part of the state tree may potentially have changed. You may then
    * call `getState()` to read the current state tree inside the callback.
    *
    * You may call `dispatch()` from a change listener, with the following
    * caveats:
    *
    * 1. The subscriptions are snapshotted just before every `dispatch()` call.
    * If you subscribe or unsubscribe while the listeners are being invoked, this
    * will not have any effect on the `dispatch()` that is currently in progress.
    * However, the next `dispatch()` call, whether nested or not, will use a more
    * recent snapshot of the subscription list.
    *
    * 2. The listener should not expect to see all state changes, as the state
    * might have been updated multiple times during a nested `dispatch()` before
    * the listener is called. It is, however, guaranteed that all subscribers
    * registered before the `dispatch()` started will be called with the latest
    * state by the time it exits.
    *
    * @param {Function} listener A callback to be invoked on every dispatch.
    * @returns {Function} A function to remove this change listener.
    */  
function subscribe(listener) {
    if (typeof listener !== 'function') {
      throw new Error('Expected listener to be a function.')
    }

    let isSubscribed = true

    ensureCanMutateNextListeners()
    nextListeners.push(listener)

    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }

      isSubscribed = false

      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
  }

註釋已經說的非常明白了,註冊一個 listener 監聽函式,把他 push 到當前的監聽裡列表 nextListener 裡面,並返回一個 unsubscribe 方法用來登出當前這個監聽函式。

dispatch

function dispatch(action) {
  if (!isPlainObject(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
  }

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

  return action
}

用來分發一個 action 來改變當前的 state . 也是唯一的改變 state 的方法。接受一個用來描述動作的 action為引數,並且把這個 action 作為函式的返回值。

從程式碼前面的判斷可以看到,action 必須是一個字面量物件,並且必須包含一個 type 的屬性。

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

從這裡可以看到,如果當前正處於上一個 action 的分發階段,那麼當前這個 action 有可能會分發失敗。

後面進行當前 state 的計算,並且按順序去觸發 nextListeners 裡面的監聽函式。

replaceReducer

/**
  * Replaces the reducer currently used by the store to calculate the state.
  *
  * You might need this if your app implements code splitting and you want to
  * load some of the reducers dynamically. You might also need this if you
  * implement a hot reloading mechanism for Redux.
  *
  * @param {Function} nextReducer The reducer for the store to use instead.
  * @returns {void}
  */
function replaceReducer(nextReducer) {
  if (typeof nextReducer !== 'function') {
    throw new Error('Expected the nextReducer to be a function.')
  }

  currentReducer = nextReducer
  dispatch({ type: ActionTypes.INIT })
}

替換掉當前的 reducer 並且分發一個用來初始化的內部 action.

export const ActionTypes = {
  INIT: '@@redux/INIT'
}

observable

/**
  * Interoperability point for observable/reactive libraries.
  * @returns {observable} A minimal observable of state changes.
  * For more information, see the observable proposal:
  * https://github.com/tc39/proposalobservable
  */
function observable() {
  const outerSubscribe = subscribe
  return {
    /**
      * The minimal observable subscription method.
      * @param {Object} observer Any object that can be used as an observer.
      * The observer object should have a `next` method.
      * @returns {subscription} An object with an `unsubscribe` method that can
      * be used to unsubscribe the observable from the store, and prevent further
      * emission of values from the observable.
      */
    subscribe(observer) {
      if (typeof observer !== 'object') {
        throw new TypeError('Expected the observer to be an object.')
      }

      function observeState() {
        if (observer.next) {
          observer.next(getState())
        }
      }

      observeState()
      const unsubscribe = outerSubscribe(observeState)
      return { unsubscribe }
    },

    [$$observable]() {
      return this
    }
  }
}

用來把一個物件變成可 observe 的方法,一般情況下用不到。

最後

// When a store is created, an "INIT" action is dispatched so that every
// reducer returns their initial state. This effectively populates
// the initial state tree.
dispatch({ type: ActionTypes.INIT })

return {
  dispatch,
  subscribe,
  getState,
  replaceReducer,
  [$$observable]: observable
}

分發一個 INIT 的初始化 action ,用來讓所有的 reducer 來返回預設的初始化 state.

然後把上面的函式返回出來,作為通過 createStore 創建出來的 store 的 api.

combineReducers

這個模組用來合併多個 reducers 到一個 reducer,它的依賴模組:

  • ActionTypes
  • isPlainObject
  • warning

我們依次來看看 combineReducers 裡面的內容。

getUndefinedStateErrorMessage

function getUndefinedStateErrorMessage(key, action) {
  const actionType = action && action.type
  const actionName = (actionType && `"${actionType.toString()}"`) || 'an action'

  return (
    `Given action ${actionName}, reducer "${key}" returned undefined. ` +
    `To ignore an action, you must explicitly return the previous state. ` +
    `If you want this reducer to hold no value, you can return null instead of undefined.`
  )
}

定義一個用來生成當 reducer 返回 undefined 時錯誤內容的函式,沒什麼好說的。

getUnexpectedStateShapeWarningMessage

function getUnexpectedStateShapeWarningMessage(inputState, reducers, action, unexpectedKeyCache) {
  const reducerKeys = Object.keys(reducers)
  const argumentName = action && action.type === ActionTypes.INIT ?
    'preloadedState argument passed to createStore' :
    'previous state received by the reducer'

  if (reducerKeys.length === 0) {
    return (
      'Store does not have a valid reducer. Make sure the argument passed ' +
      'to combineReducers is an object whose values are reducers.'
    )
  }

  if (!isPlainObject(inputState)) {
    return (
      `The ${argumentName} has unexpected type of "` +
      ({}).toString.call(inputState).match(/\s([az|AZ]+)/)[1] +
      `". Expected argument to be an object with the following ` +
      `keys: "${reducerKeys.join('", "')}"`
    )
  }

  const unexpectedKeys = Object.keys(inputState).filter(key =>
    !reducers.hasOwnProperty(key) &&
    !unexpectedKeyCache[key]
  )

  unexpectedKeys.forEach(key => {
    unexpectedKeyCache[key] = true
  })

  if (unexpectedKeys.length > 0) {
    return (
      `Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
      `"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
      `Expected to find one of the known reducer keys instead: ` +
      `"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
    )
  }
}

從函式名 “獲取未期望 State 結構錯誤資訊” 可以看出這個函式用來生成當傳入的 inputState 組成結構錯誤時的錯誤資訊。

Reducer 必須有 key 值(這不廢話),inputState 必須是一個字面量物件。且inputState 的 key 都應該在 reducer 的自身屬性(OwnProperty, 非原型鏈上的)中,並且不能在傳入的 unexpectedKeyCache 中。

assertReducerShape

function assertReducerShape(reducers) {
  Object.keys(reducers).forEach(key => {
    const reducer = reducers[key]
    const initialState = reducer(undefined, { type: ActionTypes.INIT })

    if (typeof initialState === 'undefined') {
      throw new Error(
        `Reducer "${key}" returned undefined during initialization. ` +
        `If the state passed to the reducer is undefined, you must ` +
        `explicitly return the initial state. The initial state may ` +
        `not be undefined. If you don't want to set a value for this reducer, ` +
        `you can use null instead of undefined.`
      )
    }

    const type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.')
    if (typeof reducer(undefined, { type }) === 'undefined') {
      throw new Error(
        `Reducer "${key}" returned undefined when probed with a random type. ` +
        `Don't try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` +
        `namespace. They are considered private. Instead, you must return the ` +
        `current state for any unknown actions, unless it is undefined, ` +
        `in which case you must return the initial state, regardless of the ` +
        `action type. The initial state may not be undefined, but can be null.`
      )
    }
  })
}

用來保證傳入的 reducers 的結構正確,也就說說每個 reducer 都必須在收到 INIT action 後返回一個不為 undefined 的 initState ,並且這個 action 不能在 reducer 中專門去處理。這也是為什麼我們在 reducer 裡面一定要指定預設返回的 state 的原因.

combineReducers

/**
  * Turns an object whose values are different reducer functions, into a single
  * reducer function. It will call every child reducer, and gather their results
  * into a single state object, whose keys correspond to the keys of the passed
  * reducer functions.
  *
  * @param {Object} reducers An object whose values correspond to different
  * reducer functions that need to be combined into one. One handy way to obtain
  * it is to use ES6 `import * as reducers` syntax. The reducers may never return
  * undefined for any action. Instead, they should return their initial state
  * if the state passed to them was undefined, and the current state for any
  * unrecognized action.
  *
  * @returns {Function} A reducer function that invokes every reducer inside the
  * passed object, and builds a state object with the same shape.
  */
export default function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]

    if (process.env.NODE_ENV !== 'production') {
      if (typeof reducers[key] === 'undefined') {
        warning(`No reducer provided for key "${key}"`)
      }
    }

    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)

  let unexpectedKeyCache
  if (process.env.NODE_ENV !== 'production') {
    unexpectedKeyCache = {}
  }

  let shapeAssertionError
  try {
    assertReducerShape(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }

  return function combination(state = {}, action) {
    if (shapeAssertionError) {
      throw shapeAssertionError
    }

    if (process.env.NODE_ENV !== 'production') {
      const warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache)
      if (warningMessage) {
        warning(warningMessage)
      }
    }

    let hasChanged = false
    const nextState = {}
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      const previousStateForKey = state[key]
      const nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}

combineReducer 接收一個用來合併成一個 reducer 的物件,執行後返回一個函式,也即是我們的 rootReducer .

首先把傳入的 reducers 按 key 遍歷後賦值給 finalReducers . 然後進行一堆錯誤判斷,最後返回一個函式 combination. 也就是合併後的 reducer :

let hasChanged = false
const nextState = {}
// 遍歷 finalReducerKeys
for (let i = 0; i < finalReducerKeys.length; i++) {
// 拿到當前的 reducer key
    const key = finalReducerKeys[i]
// 根據 reducer key 拿到具體的 reducer 函式
      const reducer = finalReducers[key]
// 獲取之前的 key 對應的 state
      const previousStateForKey = state[key]
// 計算下一個當前 key 對應的 state
      const nextStateForKey = reducer(previousStateForKey, action)
// 如果計算出來的 state 為 undefined 那麼報錯
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
// 把當前 key 對應的 state 賦值到下一個全域性 state
      nextState[key] = nextStateForKey
// 只要有一個 key 對應的 state 發生了變化,那麼就認為整個 state 發生了變化
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
// 根據 state 是否發生變化,返回下一個 state 或者上一個 state
    return hasChanged ? nextState : state
  }

bindActionCreators

這個函式非常簡單,是一個輔助函式。用來把 dispatch 繫結到一個 actionCreator 上,這樣當就可以通過直接呼叫繫結後的函式來分發一個 action ,而不需要 dispatch(actionCreator(…)) 了。

applyMiddleware

這裡是重點,也是一般初學者難以理解的地方,我們仔細看看。

import compose from './compose'

/**
  * Creates a store enhancer that applies middleware to the dispatch method
  * of the Redux store. This is handy for a variety of tasks, such as expressing
  * asynchronous actions in a concise manner, or logging every action payload.
  *
  * See `reduxthunk` package as an example of the Redux middleware.
  *
  * Because middleware is potentially asynchronous, this should be the first
  * store enhancer in the composition chain.
  *
  * Note that each middleware will be given the `dispatch` and `getState` functions
  * as named arguments.
  *
  * @param {...Function} middlewares The middleware chain to be applied.
  * @returns {Function} A store enhancer applying the middleware.
  */
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: (action) => dispatch(action)
    }
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

 

程式碼量非常短,依賴了模組 compose .

applyMiddleware 函式接受一系列中介軟體函式作為引數,返回了一個擁有 createStore 方法的閉包函式。這個函式,接收 reducer ,preloadedState 和 enhancer 為引數。
配合 createStore 函式來看:

export default function createStore(reducer, preloadedState, enhancer) {
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

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

    return enhancer(createStore)(reducer, preloadedState)
  }

 

當我們這樣建立 store 的時候:

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

 

createStore 的第二個引數是個函式,所以就會走到

return enhancer(createStore)(reducer, preloadedState)

 

也就是由 applyMiddleware(…middleware) 的結果接管了 createStore , 實際的 store 是在 applyMiddleware 裡面再次呼叫 createStore 建立的,此時傳入的 preloadedState, enhancer 都是 undefined.

// applyMiddleware
const store = createStore(reducer, preloadedState, enhancer)

 

回過頭來繼續往下看,

//applyMiddleware
dispatch = compose(...chain)(store.dispatch)

 

這裡需要先看一下 compose 這個模組,它的作用就是達到 compose(f, g, h) > (...args) => f(g(h(...args))) 這麼一個目的。

那麼這裡的 dispatch 就是在 store.dispatch 基礎上經過 middleware 加強封裝後的 dispatch.

const middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
}
// 把 middlewareAPI 傳入到每個中介軟體中
chain = middlewares.map(middleware => middleware(middlewareAPI))

 

這裡的 dispatch: (action) => dispatch(action) ,說明每個中介軟體中的 dispatch 都是獨立互不影響的,以免某個中介軟體中修改了 dispatch 的行為。然後給每個中介軟體都傳入 getState 和 dispatch 作為他們的引數。

return {
  ...store,
  dispatch
}

 

最後用加強後的 dispatch 覆蓋掉原有 store 中的 dispatch.

整個中介軟體的程式碼看下來,可能比較抽象,我們結合一個例子來看一下:

errorMiddleware

export default ({dispatch, getState}) => next => action => {
  const {error, payload} = action
  if (error) {
    dispatch(showToast(payload.message || payload.toString()))
  }
  return next(action)
}

 

這是我們的一個錯誤處理中介軟體。它也是一個高階函式,首先接受 dispatchgetState 為引數,返回一個接受 next 為引數的函式。dispatchgetState 就是在上面程式碼裡通過 middlewareAPI 傳入了中介軟體中。

然後我們繼續看 errorMiddleware 執行後返回的接受 next 為引數的函式,而 next 其實就是下一個要執行的 middleware .

然後我們需要了解一下中介軟體的執行順序,那麼為了更清楚的描述一個 action 在中介軟體中的傳播過程,我們假設有以下三個中介軟體:

const mid1 = () => next => action => {
  console.log('mid1 before')
  next(action)
  console.log('mid1 after')
}
const mid2 = () => next => action => {
  console.log('mid2 before')
  next(action)
  console.log('mid2 after')
}
const mid3 = () => next => action => {
  console.log('mid3 before')
  next(action)
  console.log('mid3 after')
}

執行 applyMiddleware( mid1, mid2, mid3 ), 那麼經過下面程式碼後

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

 

就可以得到:

dispatch = (store.dispatch) => mid1(mid2(mid3(store.dispatch)))

其中的 midx 都是已經執行了 middleware(middlewareAPI) 後返回的結果。所以 mid3 的 next 的值就是 store.dispatch 。而 mid2 的 next 則是 mid3(store.dispatch) ,以此類推,mid1 的 next 就是 mid2(mid3(store.dispatch)) , 這也就是在 middleware 呼叫 next 能夠讓 action 轉到下一個 middleware 的原因。

當我們分發一個 action 時,控制檯打印出來的順序是這樣的:

mid1 before
mid2 before
mid3 before
mid3 after
mid2 after
mid1 after

可以看到它的流程是這樣的:

  1. 執行 mid1 中 next 方法呼叫之前的程式碼
  2. 執行 mid2 中 next 方法呼叫之前的程式碼
  3. 執行 mid3 中 next 方法呼叫之前的程式碼
  4. 執行 dispatch 來分發 action
  5. 執行 mid3 中 next 方法呼叫之後的程式碼
  6. 執行 mid2 中 next 方法呼叫之後的程式碼
  7. 執行 mid1 中 next 方法呼叫之後的程式碼

看一張圖,會更明白一點:

其中紅色的路徑就是我們剛才描述的流程。可以看到其中還有一條黑色路徑,也就是如果我們直接在 mid2 中呼叫 dispatch 會怎麼樣?我們來改一下 mid2

const mid2 = ({ dispatch, getStore }) => next => action => {
  console.log('mid2 before')
  dispatch(action)
  console.log('mid2 after')
}

改成這樣,猜猜會怎樣?

答案是,會一直在 mid1 before 和 mid2 before 中死迴圈,因為呼叫的 dispatch 會讓這個 action 重新走一遍所有的中介軟體,也就是圖中的黑色路徑。那麼當我們需要在一箇中間件中呼叫 dispatch 的時候,是要對 action 做判斷的,只有滿足某個條件的時候才呼叫 dispatch 以免出現死迴圈。改造一下 mid2

const mid2 = ({ dispatch, getStore }) => next => action => {
  console.log('mid2 before')
  if(action.isApi) {
    dispatch({
      isApi: false,
      ...
    })
  }
  dispatch(action)
  console.log('mid2 after')
}

這樣,就只有在 action 滿足 isApi 條件的時候才會取分發一個不滿足 isApi 條件的 action ,這樣就不會死迴圈。一般在非同步分發 action 的時候會經常用這個方法。比如我們生產環境用來請求資料的 callAPIMiddleware :

export default ({dispatch, getState}) => {
return next => action => {
  const {
    types,
    api,
    callType,
    meta,
    body,
    shouldCallAPI
  } = action
  const state = getState()
  const callTypeList = ['get', 'post']
  if (!api) {
    return next(action)
  }
  if (!(types.start && types.success && types.failure)) {
    throw new Error('Expected types has start && success && failure keys.')
  }
  if (callTypeList.indexOf(callType) === 1) {
    throw new Error(`API callType Must be one of ${callTypeList}`)
  }

  const {start, success, failure} = types
  if (!shouldCallAPI(state)) {
    return false
  }

  dispatch({
    type: start,
    payload: {
      ...body
    },
    meta
  })
  const mapCallTypeToFetch = {
      post: () => fetch(api, {
        method: 'post',
        // credentials 設定為每次請求都帶上 cookie
        credentials: 'include',
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(bodyWithSource)
      }),
      get: () => {
        const toString = Object.keys(bodyWithSource).map(function (key, index) {
          return encodeURIComponent(key) + '=' + encodeURIComponent(bodyWithSource[key])
        }).join('&')
        return fetch(`${api}?${toString}`, {
          method: 'get',
          credentials: 'include',
          headers: {
            'Accept': 'application/json'
          }
        })
      }
    }
    const fetching = mapCallTypeToFetch[callType]()
... 省略一堆業務邏輯
  return fetching.then(res => {
    clearTimeout(loadingTimer)
    dispatch(hideLoading())
    if (res.ok) {
      try {
        return res.json()
      } catch (err) {
        throw new Error(err)
      }
    } else {
      dispatch(showToast('請求出錯'))
      return Promise.reject(res.text())
    }
  })
    .then(res => resBehaviour(res))
    .then(res => {
      dispatch({
        type: success,
        meta,
        payload: {
          ...res.data
        }
      })
      return Promise.resolve(res)
    })
    .catch(err => {
      console.error(`介面請求出錯,${err}`)
      return Promise.reject(err)
    })
}

關於中介軟體就說這麼多,大家應該也能理解了。

總結

總體上看, Redux 的原始碼非常短,但是各種實現都非常的精巧。

而且作者非常重視對開發者的體驗,註釋非常的詳細,整體上讀起來比較輕鬆。錯誤處理也非常詳細,可以幫助開發者更容易的定位錯誤。

最後,由於本人能力有限,文中如果有錯誤的地方,還請指出一起討論。

 

原文https://blog.kisnows.com/2018/11/20/redux-source-code-read/?utm_source=tuicool&utm_medium=referral