Redux源碼學習筆記

https://github.com/reduxjs/redux 版本 4.0.0html

 

先了解一下redux是怎麼用的,此處摘抄自阮一峯老師的《Redux 入門教程git

// Web 應用是一個狀態機,視圖與狀態是一一對應的
// 全部的狀態,保存在一個對象裏面

// store 是保存數據的地方

// 建立 store
import { createStore } from 'redux'
const store = createStore(fn)

// state 是某一時刻 store 的快照,一個 state 對應一個 view
// 可經過 getState() 獲取
const state = store.getState()

// Action 是一個對象 用來表示 view 發出的改變 state 的通知
// type 是必須的 其餘屬性能夠自由設置
const action = {
    type: 'ADD_TODO',
    payload: 'Learn Redux'
}

// 同一種類型的 action 能夠寫一個函數生成
const ADD_TODO = '添加 TODO'
// 生成 action 的函數: Action Creator
function addTodo(text) {
    return {
        type: ADD_TODO,
        text
    }
}

const action = addTodo('Learn Redux')

// store.dispatch()是 View 發出 Action 的惟一方法。
store.dispatch(action)

// reducer 是 store 接收 state 返回新的 state 的過程

const defaultState = 0
// reducer 接收 action 返回新的 state
const reducer = (state = defaultState, action) => {
    switch(action.type) {
        case: 'ADD':
            return state + action.payload
        default:
            return state
    }
}
const state = reducer(1, {
    type: 'ADD',
    payload: 2
})

// 建立 store 時傳入 reducer 當調用 store.dispatch 時將自動調用 reducer
const store = createStore(reducer)

/*
reducer 是一個純函數,純函數要求:
- 不得改寫參數
- 不能調用系統 I/O 的API
- 不能調用Date.now()或者Math.random()等不純的方法,由於每次會獲得不同的結果
*/

// store.subscribe方法設置監聽函數,一旦 State 發生變化,就自動執行這個函數
// 返回解除監聽函數
let unsubscribe = store.subsribe(() => { console.log(store.getState) })
unsubscribe() // 解除監聽

/*
store 提供的三個方法
- store.getState()
- store.dispatch()
- store.subscribe()
*/

// createStore方法還能夠接受第二個參數,表示 State 的最初狀態。這一般是服務器給出的。
// !這個初始值會覆蓋 Reducer 函數默認的初始值
let store = createStore(todoApp,  STATE_FROM_SERVER)

// createStore 的簡單實現
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 }

}

// 能夠經過 combineReducers 來將多個 Reducer 合爲一個
import { combineReducers } from 'redux'

const chatReducer = combineReducers({
    chatLog,
    statusMessage,
    userName
})

// combineReducer 的簡單實現
const combineReducers = reducers => {
    return (state = {}, action) => 
        Object.keys(reducers).reduce(
            (nextState, key) => {
                nextState[key] = reducers[key](state[key], action)
                return nextState
            },
            {}
        )
}

 

工做流程github

Redux Flow

        dispatch(action)   (previousState, action)
Action Creators ======> Store ======> Reducers
   ^                     ||   <======
    \_                   ||   (newState)
      \_         (state) ||
        \_               ||
(view opt)\_             \/
            \---   React Comonents

 

 

OK 能夠開始看源碼了~ 網上Redux源碼分析的博客真的很是多.. 不過當你知道他的源碼究竟有多短 就能理解了hhh web

combineReducers.js express

代碼一共179行 可能是錯誤處理 我先將錯誤處理所有刪掉 便只剩28行.....編程

思路就是建立一個對象 將 Reducer 所有放進去redux

當Action傳進來的時候 就讓每個Reducer去處理這個actionapi

每一個Reducer都有一個對應的key 只處理state中對應字段 state[key] 沒有Reducer對應的字段會被忽略服務器

截取出核心代碼 + 用法、感受並不須要註釋、邏輯都很直接app

function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]

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


  return function combination(state = {}, action) {
    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)

      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    // 若是state每個key都沒有被修改 就直接返回原state
    return hasChanged ? nextState : state
  }
}

/***************** 下面是簡單的用法實例 *****************/
function todos(state = [], action) {
  switch (action.type) {
    case 'ADD_TODO':
      return state.concat(action.text)
    default:
      return state
  }
}

function counter(state = 0, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1
    case 'DECREMENT':
      return state - 1
    default:
      return state
  }
}

let reducer = combineReducers({ list: todos, number: counter })
let state = { list: [], number: 0, otherKey: 'no reducer match will be ignore' }
console.log(state) // { list: [], number: 0, otherKey: 'no reducer match will be ignore' }
state = reducer(state, { type: 'ADD_TODO', text: 'study' })
console.log(state) // { list: [ 'study' ], number: 0 }
state = reducer(state, { type: 'ADD_TODO', text: 'sleep' })
console.log(state) // { list: [ 'study', 'sleep' ], number: 0 }
state = reducer(state, { type: 'INCREMENT' })
console.log(state) // { list: [ 'study', 'sleep' ], number: 1 }

 

combineReducers.js 源碼

import ActionTypes from './utils/actionTypes'
import warning from './utils/warning'
import isPlainObject from './utils/isPlainObject'

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

  return (
    `Given ${actionDescription}, 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.`
  )
}

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)) {
    // 但願 inputState 是一個簡單對象:經過 new Object() 、 {} 建立 (Object.create(null) 這裏好像是不合法的
    // [object Array] 中提取 'Array'
    // Object.prototype.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1]
    return (
      `The ${argumentName} has unexpected type of "` +
      {}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] +
      `". Expected argument to be an object with the following ` +
      `keys: "${reducerKeys.join('", "')}"`
    )
  }
  // 檢查全部Reducer都沒有處理到的key ( 此處實在不解 unexpectedKeyCache 到底何用= =
  const unexpectedKeys = Object.keys(inputState).filter(
    key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
  )

  unexpectedKeys.forEach(key => {
    unexpectedKeyCache[key] = true
  })
  // 替換 store 的 Reducer 時會調用 dispatch({ type: ActionTypes.REPLACE })
  if (action && action.type === ActionTypes.REPLACE) return

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

function assertReducerShape(reducers) {
  Object.keys(reducers).forEach(key => {
    const reducer = reducers[key]
    const initialState = reducer(undefined, { type: ActionTypes.INIT })
      // Reducer"$ {key}"在初始化時返回undefined。若是傳遞給reducer的狀態未定義,你必須明確返回初始狀態。
      // 初始狀態能夠是不可定義。若是你不想爲這個reducer設置一個值,你可使用null而不是undefined。
    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.`
      )
    }

    if (
      typeof reducer(undefined, {
        type: ActionTypes.PROBE_UNKNOWN_ACTION()
      }) === 'undefined'
    ) {
      // 當使用隨機類型探測Reducer${key}時返回undefined。
      // 不要試圖處理${ActionTypes.INIT}或者其餘在"redux/*"命名空間的動做。它們被認爲是私有的。
      // 相反,當你遇到任何未知動做時,你必須返回當前的state,除非當前state是undefined,
      // 那樣你要返回初始狀態,而無論動做類型。初始狀態不能夠是undefined,但能夠爲null
      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.`
      )
    }
  })
}

/**
 * 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 {
    // 判斷每一個reducer都有初始值和對於未知action返回原state
    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
    }
    // 若是state每個key都沒有被修改 就直接返回原state
    return hasChanged ? nextState : state
  }
}
View Code

 

utils/actionTypes.js

// 生成隨機字符串的方式能夠參考下
// 隨機數轉36進制 能夠生成 '0-9a-z' 的隨機字符串
const randomString = () =>
  Math.random()
    .toString(36)
    .substring(7)
    .split('')
    .join('.')
// 私有action類型 (其實就至關於未知的action 返回當前狀態就行了
// 若是當前 state 爲undefined 就返回 Reducer設置的初始 state
const ActionTypes = {
  INIT: `@@redux/INIT${randomString()}`,
  REPLACE: `@@redux/REPLACE${randomString()}`,
  PROBE_UNKNOWN_ACTION: () => `@@redux/PROBE_UNKNOWN_ACTION${randomString()}`
}

export default ActionTypes

 

createStore.js 

是redux核心代碼,不過這個沒有什麼難理解的地方

import $$observable from 'symbol-observable'

import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'

// 建立 store 的函數
// preloadedState: store設置的初始值  這個值會覆蓋 Reducer 的默認值
// 若是使用了 combineReducers preloadedState 要和 combineReducers 有相同的keys
// enhancer: 中間件
export default function createStore(reducer, preloadedState, enhancer) {
  // preloadedState能夠不傳 判斷preloadedState是否存在
  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.')
    }
    // enhancer是一個高階函數 調用enhancer返回一個"增強版"的createStore
    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
  // 判斷當前 nextListeners 和 currentListeners 是否爲同一個對象
  // 若是是一個對象 就把 nextListeners 改成 currentListeners 的副本
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }
  // 獲取當前對象 若是是正在派發action 則不能獲取state
  function getState() {
    if (isDispatching) {
      throw new Error(
        'You may not call store.getState() while the reducer is executing. ' +
          'The reducer has already received the state as an argument. ' +
          'Pass it down from the top reducer instead of reading it from the store.'
      )
    }

    return currentState
  }
  // 訂閱 添加訂閱者
  function subscribe(listener) {
    if (typeof listener !== 'function') {
      throw new Error('Expected the listener to be a function.')
    }

    if (isDispatching) {
      throw new Error(
        'You may not call store.subscribe() while the reducer is executing. ' +
          'If you would like to be notified after the store has been updated, subscribe from a ' +
          'component and invoke store.getState() in the callback to access the latest state. ' +
          'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
      )
    }

    let isSubscribed = true
    // 每次修改 nextListeners 都要判斷一下 nextListeners 和 currentListeners 是否爲同一個對象
    ensureCanMutateNextListeners()
    // 注意 這裏修改 nextListeners 以後並無改變 currentListeners 而是在下一次用到 currentListeners 纔會改變
    nextListeners.push(listener)

    // 返回一個當前監聽者取消訂閱的方法
    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }
      // 正在派發 action 時不能進行操做
      if (isDispatching) {
        throw new Error(
          'You may not unsubscribe from a store listener while the reducer is executing. ' +
            'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
        )
      }

      isSubscribed = false

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


  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 記錄是否正在 派發action 過程當中不能進行其餘操做
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }
    // 用到 listeners 纔會修改 currentListeners 以減小修改次數
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

  // 替換 Reducer 並派發動做 ActionTypes.REPLACE 至關於對state從新進行初始化
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }

    currentReducer = nextReducer
    dispatch({ type: ActionTypes.REPLACE })
  }
  // emmmm...看不懂這個 能夠參考 https://distums.github.io/2017/03/19/observables-proposal-for-ecmascript/
  function observable() {
    const outerSubscribe = subscribe
    return {
      subscribe(observer) {
        if (typeof observer !== 'object' || observer === null) {
          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
      }
    }
  }

  dispatch({ type: ActionTypes.INIT })

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

 

bindActionCreators.js

此處參考 《mapStateToProps,mapDispatchToProps的使用姿式

按註釋上說 這只是一個 convenience method

你能夠把 store.dispatch(MyActionCreators.doSomething()) 換成一個轉成一個函數

咱們使用 action 時 是先經過 actionCreator建立action 而後經過 dispatch 派發出去

經過 bindActionCreator(actionCreator, dispatch) 得到一個能夠直接建立action並派發的函數

bindActionCreators 就是建立一個對象 每一個屬性都是一個 能夠直接建立action並派發的函數

例:

action.increase = (info) => { type:'INCREASE',info }
action.decrease = (info) => { type:'DECREASE',info }

bindActionCreators({
increase: action.increase,
decrease: action.decrease
}, dispatch)

// 就能夠得到:
{
increase: (...args) => dispatch(action.increase(...args)),
decrease: (...args) => dispatch(action.decrease(...args))
}

 

源碼:

function bindActionCreator(actionCreator, dispatch) {
  return function() {
    return dispatch(actionCreator.apply(this, arguments))
  }
}

/**
 * Turns an object whose values are action creators, into an object with the
 * same keys, but with every function wrapped into a `dispatch` call so they
 * may be invoked directly. This is just a convenience method, as you can call
 * `store.dispatch(MyActionCreators.doSomething())` yourself just fine.
 *
 * For convenience, you can also pass a single function as the first argument,
 * and get a function in return.
 *
 * @param {Function|Object} actionCreators An object whose values are action
 * creator functions. One handy way to obtain it is to use ES6 `import * as`
 * syntax. You may also pass a single function.
 *
 * @param {Function} dispatch The `dispatch` function available on your Redux
 * store.
 *
 * @returns {Function|Object} The object mimicking the original object, but with
 * every action creator wrapped into the `dispatch` call. If you passed a
 * function as `actionCreators`, the return value will also be a single
 * function.
 */
export default function bindActionCreators(actionCreators, dispatch) {
  // 若是 actionCreators 是一個函數 說明只有一個 actionCreator
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }

  if (typeof actionCreators !== 'object' || actionCreators === null) {
    throw new Error(
      `bindActionCreators expected an object or a function, instead received ${
        actionCreators === null ? 'null' : typeof actionCreators
      }. ` +
        `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
    )
  }

  const keys = Object.keys(actionCreators)
  const boundActionCreators = {}
  for (let i = 0; i < keys.length; i++) {
    const key = keys[i]
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}

 

applyMiddleware.js

這個應該是最難理解的部分 因此放到最後看>_<

我的理解,這個東西就是在dispatch先後作一些事情=.= 相似koa express的中間件嘛

如下參考 源碼中 redux/docs/advanced/Middleware.md

middleware 在dispatch和action之間提供一個第三方程序擴展點。

如今一步一步理解applyMiddleware在作什麼

首先,假設如今有一個需求,每次dispatch一個action時,都要打印action和state,像下面這樣:

const action = addTodo('Use Redux')

console.log('dispatching', action)
store.dispatch(action)
console.log('next state', store.getState())

可是不可能每一次都這樣打印,也許直接修改dispatch就能夠

const next = store.dispatch
store.dispatch = function dispatchAndLog(action) {
  console.log('dispatching', action)
  let result = next(action)
  console.log('next state', store.getState())
  return result
}

吶,可能不止一個需求,如今我又想記錄錯誤信息了。咱們寫兩個方法,分別給dispatch添加本身想要的功能。

function patchStoreToAddLogging(store) {
  const next = store.dispatch
  store.dispatch = function dispatchAndLog(action) {
    console.log('dispatching', action)
    let result = next(action)
    console.log('next state', store.getState())
    return result
  }
}

function patchStoreToAddCrashReporting(store) {
  const next = store.dispatch
  store.dispatch = function dispatchAndReportErrors(action) {
    try {
      return next(action)
    } catch (err) {
      console.error('Caught an exception!', err)
      Raven.captureException(err, {
        extra: {
          action,
          state: store.getState()
        }
      })
      throw err
    }
  }
}

patchStoreToAddLogging(store)
patchStoreToAddCrashReporting(store)

可是這樣並很差……很明顯,咱們在修改store的私有屬性了,emmm……這是一個比較hack的方法……要改的優雅一點,把修改dispatch的部分封裝起來。每一次返回新的dispatch,修改store的部分由 applyMiddlewareByMonkeypatching 統一處理。

function logger(store) {
  const next = store.dispatch
  // Previously:
  // store.dispatch = function dispatchAndLog(action) {
  return function dispatchAndLog(action) {
    console.log('dispatching', action)
    let result = next(action)
    console.log('next state', store.getState())
    return result
  }
}
 
function applyMiddlewareByMonkeypatching(store, middlewares) {
  middlewares = middlewares.slice()
  middlewares.reverse()
  // Transform dispatch function with each middleware.
  middlewares.forEach(middleware =>
    store.dispatch = middleware(store)
  )
}

applyMiddlewareByMonkeypatching(store, [logger, crashReporter])

可是這樣仍是不太好。dispatch是store的私有屬性,咱們卻直接獲取了。思考咱們爲何重寫dispatch,由於咱們在用多箇中間件的時候,第一個中間件修改完dispatch,下一次修改應該是在前一個的基礎之上,包裹上一次修改的dispatch。但其實,這也不是必要的,只要每一次傳入上一次修改後的dispatch就能夠了。

function logger(store) {
  return function wrapDispatchToAddLogging(next) {
    return function dispatchAndLog(action) {
      console.log('dispatching', action)
      let result = next(action)
      console.log('next state', store.getState())
      return result
    }
  }
}

這裏的next就是以前的中間件處理後的dispatch,咱們再也不獲取store的私有屬性了,改成用參數傳遞。而後在處理以後(logger(store)(next))返回一個新的dispatch。

爲何這裏要套兩個函數而不是傳入兩個參數(store, next)呢,就至關於把這個函數柯里化了嘛……後面能夠看到用處。

改爲ES6的箭頭函數

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

說實話雖然簡潔了,可是看起來一點都不直觀……多是我太菜了。嗯,這就是一箇中間件的寫法了。

能夠簡單的實現下 applyMiddleware

function applyMiddleware(store, middlewares) {
  middlewares = middlewares.slice()
  middlewares.reverse()
  let dispatch = store.dispatch
  middlewares.forEach(middleware =>
    dispatch = middleware(store)(dispatch)
  )
  return Object.assign({}, store, { dispatch })
}

這樣就能夠最後使用 applyMiddleware

import { createStore, combineReducers, applyMiddleware } from 'redux'

const todoApp = combineReducers(reducers)
const store = createStore(
  todoApp,
  // applyMiddleware() tells createStore() how to handle middleware
  applyMiddleware(logger, crashReporter)
)

深刻(meiyou)的理解以後 開始看applyMiddleware.js源碼

其中用到裏 compose 要先看一下

compose.js

這個是函數式編程的一個……思想?應用?

將函數的嵌套調用寫成組合  compose(b, c, a) 至關於   b(c(a(x)))

export default function compose(...funcs) {
  if (funcs.length === 0) { return arg => arg } if (funcs.length === 1) { return funcs[0] } // reduce的參數.. // reduce(function(accumulator, currentValue, currentIndex, array) {...}) return funcs.reduce((a, b) => (...args) => a(b(...args))) } /********** 使用示例 **********/ let a = x => 'a' + x + 'a' let b = x => 'b' + x + 'b' let c = x => 'c' + x + 'c' let foo = compose(b, c, a) console.log(foo('v')) // bcavacb let bar = x => b(c(a(x))) console.log(bar('v')) // bcavacb

 最後看applyMiddleware.js

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 `redux-thunk` 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 => (...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([middlewares]) 就是返回一個函數 傳入createStore,返回新的createStore,建立的store的dispatch是通過中間件加工的。

這裏能夠看到編寫中間件嵌套兩個函數的用處,先傳入一個store,只須要再傳入一個最新的dispatch就能夠了,就是把dispatch用中間件輪流處理一下。這裏使用了compose。

 

勉強看完源碼。僞裝本身理解了這樣子。

相關文章
相關標籤/搜索