React系列 --- 擴展狀態管理功能及Redux源碼解析(八)

React系列

React系列 --- 簡單模擬語法(一)
React系列 --- Jsx, 合成事件與Refs(二)
React系列 --- virtualdom diff算法實現分析(三)
React系列 --- 從Mixin到HOC再到HOOKS(四)
React系列 --- createElement, ReactElement與Component部分源碼解析(五)
React系列 --- 從使用React瞭解Css的各類使用方案(六)
React系列 --- 從零構建狀態管理及Redux源碼解析(七)
React系列 --- 擴展狀態管理功能及Redux源碼解析(八)html

createStore.ts源碼解析

基本功能以後,咱們再回頭看看createStore.ts裏有什麼關鍵代碼實現功能的react

import $$observable from 'symbol-observable'

import {
  Store,
  PreloadedState,
  StoreEnhancer,
  Dispatch,
  Observer,
  ExtendState
} from './types/store'
import { Action } from './types/actions'
import { Reducer } from './types/reducers'
import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'

頭部引入了symbol-observable作響應式數據,其他都是一些類型聲明和工具函數git

/**
 * Creates a Redux store that holds the state tree.
 * The only way to change the data in the store is to call `dispatch()` on it.
 *
 * There should only be a single store in your app. To specify how different
 * parts of the state tree respond to actions, you may combine several reducers
 * into a single reducer function by using `combineReducers`.
 *
 * @param reducer A function that returns the next state tree, given
 * the current state tree and the action to handle.
 *
 * @param preloadedState The initial state. You may optionally specify it
 * to hydrate the state from the server in universal apps, or to restore a
 * previously serialized user session.
 * If you use `combineReducers` to produce the root reducer function, this must be
 * an object with the same shape as `combineReducers` keys.
 *
 * @param enhancer The store enhancer. You may optionally specify it
 * to enhance the store with third-party capabilities such as middleware,
 * time travel, persistence, etc. The only store enhancer that ships with Redux
 * is `applyMiddleware()`.
 *
 * @returns A Redux store that lets you read the state, dispatch actions
 * and subscribe to changes.
 */

函數註釋來看有三個入參github

參數 描述
reducer 給與當前state和action返回新的state
preloadedState 初始化state,能夠從服務器獲取或者恢復之前用戶序列化的緩存數據
enhancer 能夠指定例如中間件的第三方功能加強

返回的store可讓你讀取state, 觸發actions,監聽變化算法

-------------省略部分代碼----------------
if (
  (typeof preloadedState === 'function' && typeof enhancer === 'function') ||
  (typeof enhancer === 'function' && typeof arguments[3] === 'function')
) {
  throw new Error(
    'It looks like you are passing several store enhancers to ' +
      'createStore(). This is not supported. Instead, compose them ' +
      'together to a single function.'
  )
}

if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
  enhancer = preloadedState as StoreEnhancer<Ext, StateExt>
  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 as PreloadedState<
    S
  >) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
}

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

都是一些基本的判斷和報錯機制,也是咱們手寫代碼省略掉的一步,中間有關於enhancer部分的代碼能夠後面再講express

let currentReducer = reducer
let currentState = preloadedState as S
let currentListeners: (() => void)[] | null = []
let nextListeners = currentListeners
let isDispatching = false

/**
 * This makes a shallow copy of currentListeners so we can use
 * nextListeners as a temporary list while dispatching.
 *
 * This prevents any bugs around consumers calling
 * subscribe/unsubscribe in the middle of a dispatch.
 */
function ensureCanMutateNextListeners() {
  if (nextListeners === currentListeners) {
    nextListeners = currentListeners.slice()
  }
}

/**
 * Reads the state tree managed by the store.
 *
 * @returns The current state tree of your application.
 */
function getState(): S {
  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 as S
}

開頭是基本的聲明變量,相比較咱們多了一個nextListenersisDispatchingredux

前者是做爲currentListeners淺拷貝的臨時變量給分發階段使用的,這樣能夠避免在這過程當中會用subscribe/unsubscribe所致使的bugsegmentfault

後者是用來鎖定狀態,在dispatching的過程當中作對應邏輯api

/**
 * 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 listener A callback to be invoked on every dispatch.
 * @returns A function to remove this change listener.
 */
function subscribe(listener: () => void) {
  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#subscribelistener for more details.'
    )
  }

  let isSubscribed = true

  ensureCanMutateNextListeners()
  nextListeners.push(listener)

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

    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#subscribelistener for more details.'
      )
    }

    isSubscribed = false

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

比起咱們redux還作了幾層機制數組

  1. 參數限制
  2. isDispatching狀態控制
  3. 每次添加新的監聽事件前都會更新最新隊列去添加
  4. isSubscribed控制移除事件狀態
  5. 經過索引值移除對應事件
/**
 * Dispatches an action. It is the only way to trigger a state change.
 *
 * The `reducer` function, used to create the store, will be called with the
 * current state tree and the given `action`. Its return value will
 * be considered the **next** state of the tree, and the change listeners
 * will be notified.
 *
 * The base implementation only supports plain object actions. If you want to
 * dispatch a Promise, an Observable, a thunk, or something else, you need to
 * wrap your store creating function into the corresponding middleware. For
 * example, see the documentation for the `redux-thunk` package. Even the
 * middleware will eventually dispatch plain object actions using this method.
 *
 * @param action A plain object representing 「what changed」. It is
 * a good idea to keep actions serializable so you can record and replay user
 * sessions, or use the time travelling `redux-devtools`. An action must have
 * a `type` property which may not be `undefined`. It is a good idea to use
 * string constants for action types.
 *
 * @returns For convenience, the same action object you dispatched.
 *
 * Note that, if you use a custom middleware, it may wrap `dispatch()` to
 * return something else (for example, a Promise you can await).
 */
function dispatch(action: A) {
  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
}

更加多的邏輯代碼

  1. 參數判斷
  2. isDispatching狀態控制
  3. 純函數更新數據代碼加了捕獲機制
  4. 每次都拿最新的監聽隊列遍歷觸發
  5. 返回原樣action
/**
 * 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 nextReducer The reducer for the store to use instead.
 * @returns The same store instance with a new reducer in place.
 */
function replaceReducer<NewState, NewActions extends A>(
  nextReducer: Reducer<NewState, NewActions>
): Store<ExtendState<NewState, StateExt>, NewActions, StateExt, Ext> & Ext {
  if (typeof nextReducer !== 'function') {
    throw new Error('Expected the nextReducer to be a function.')
  }

  // TODO: do this more elegantly
  ;((currentReducer as unknown) as Reducer<
    NewState,
    NewActions
  >) = nextReducer

  // This action has a similiar effect to ActionTypes.INIT.
  // Any reducers that existed in both the new and old rootReducer
  // will receive the previous state. This effectively populates
  // the new state tree with any relevant data from the old one.
  dispatch({ type: ActionTypes.REPLACE } as A)
  // change the type of the store by casting it to the new store
  return (store as unknown) as Store<
    ExtendState<NewState, StateExt>,
    NewActions,
    StateExt,
    Ext
  > &
    Ext
}

reducers的替代方法,通常場景比較少用到,基本代碼很少

/**
 * Interoperability point for observable/reactive libraries.
 * @returns A minimal observable of state changes.
 * For more information, see the observable proposal:
 * https://github.com/tc39/proposal-observable
 */
function observable() {
  const outerSubscribe = subscribe
  return {
    /**
     * The minimal observable subscription method.
     * @param observer Any object that can be used as an observer.
     * The observer object should have a `next` method.
     * @returns 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: unknown) {
      if (typeof observer !== 'object' || observer === null) {
        throw new TypeError('Expected the observer to be an object.')
      }

      function observeState() {
        const observerAsObserver = observer as Observer<S>
        if (observerAsObserver.next) {
          observerAsObserver.next(getState())
        }
      }

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

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

觀察者模式的實現庫作監聽事件

// 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 } as A)

const store = ({
  dispatch: dispatch as Dispatch<A>,
  subscribe,
  getState,
  replaceReducer,
  [$$observable]: observable
} as unknown) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
return store

方法的最後會觸發一個ActionTypes.INIT的action作初始化數據,返回一個包含暴露的方法對象出去.

createStore.ts源碼地址

咱們再看看actionTypes.ts源碼作了什麼

/**
 * These are private action types reserved by Redux.
 * For any unknown actions, you must return the current state.
 * If the current state is undefined, you must return the initial state.
 * Do not reference these action types directly in your code.
 */

const randomString = () =>
  Math.random()
    .toString(36)
    .substring(7)
    .split('')
    .join('.')

const ActionTypes = {
  INIT: `@@redux/INIT${randomString()}`,
  REPLACE: `@@redux/REPLACE${randomString()}`,
  PROBE_UNKNOWN_ACTION: () => `@@redux/PROBE_UNKNOWN_ACTION${randomString()}`
}

export default ActionTypes

不在於獲得什麼結果,只須要它是複雜難以跟開發定義的action重複就好了,爲了執行一次distapch獲取到初始的state.

actionTypes.ts源碼地址

實例六(優化)

學習完createStore.ts源碼以後咱們能夠將一些好的地方引入咱們的庫裏

  1. nextListeners充當臨時變量傳遞給其餘函數使用
  2. isDispatching做爲狀態標記判斷流程
  3. dispatch函數增長容錯機制,返回原樣action
  4. isSubscribed控制監聽事件解綁機制,從從新過濾賦值改爲根據索引值移除事件
  5. 增長一個不易重複的action執行預觸發返回每一個reducer的初始數據

createStore.js

function createStore (initStore = {}, reducer) {
  // 惟一數據源
  let state = initStore
  // 監聽隊列
  let listenList = []
  // 監聽隊列淺拷貝
  let nextListeners = listenList
  // 是否dispatch中
  let isDispatching = false

  // 淺拷貝
  function ensureCanMutateNextListeners () {
    if (nextListeners === listenList) {
      nextListeners = listenList.slice()
    }
  }

  // 惟一獲取數據函數
  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 state
  }

  // 純函數來執行修改,只返回最新數據
  const dispatch = (action) => {
    // 嚴格控制dispatch,不得中途再次發送
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    // 增長意外防止操做
    try {
      isDispatching = true
      state = reducer(state, action)
    } finally {
      isDispatching = false
    }

    // 獲取更改後的數據同時獲取最新隊列
    const listeners = (listenList = nextListeners)
    // 替換成原始遍歷提升性能,遍歷觸發事件
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    // 爲了方便將action原樣返回
    return action
  }

  // 添加監聽器, 同時返回解綁該事件的函數
  const subscribe = (fn) => {
    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. '
      )
    }

    // 佔位標記
    let isSubscribed = true
    // 每次添加監聽事件時淺拷貝最新隊列
    ensureCanMutateNextListeners()
    nextListeners.push(fn)

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

      if (isDispatching) {
        throw new Error(
          'You may not unsubscribe from a store listener while the reducer is executing. '
        )
      }
      isSubscribed = false
      // 每次移除監聽事件時淺拷貝最新隊列
      ensureCanMutateNextListeners()
      // 根據索引值刪除比filter過濾從新賦值效率高
      const index = nextListeners.indexOf(fn)
      nextListeners.splice(index, 1)
      listenList = null
    }
  }

  // 默認觸發一次dispatch以獲取各個reduce的初始數據
  dispatch({
    type: `@@redux/INIT${Math.random()
      .toString(36)
      .substring(7)
      .split('')
      .join('.')}`
  })

  return {
    getState,
    dispatch,
    subscribe
  }
}

文章的完整代碼能夠直接查看demo6

applyMiddleware源碼解析

createStore函數還有一個入參enhancer咱們以前沒實現,

React提供使用中間件的惟一方式是applyMiddleware函數,咱們看一下怎麼介紹它的

Middleware 可讓你包裝 store 的 dispatch 方法來達到你想要的目的。同時, middleware 還擁有「可組合」這一關鍵特性。多個 middleware 能夠被組合到一塊兒使用,造成 middleware 鏈。其中,每一個 middleware 都不須要關心鏈中它先後的 middleware 的任何信息

demo

import { createStore, applyMiddleware } from 'redux'
import todos from './reducers'

function logger({ getState }) {
  return (next) => (action) => {
    console.log('will dispatch', action)

    // 調用 middleware 鏈中下一個 middleware 的 dispatch。
    let returnValue = next(action)

    console.log('state after dispatch', getState())

    // 通常會是 action 自己,除非
    // 後面的 middleware 修改了它。
    return returnValue
  }
}

let store = createStore(
  todos,
  [ 'Use Redux' ],
  applyMiddleware(logger)
)

store.dispatch({
  type: 'ADD_TODO',
  text: 'Understand the middleware'
})
// (將打印以下信息:)
// will dispatch: { type: 'ADD_TODO', text: 'Understand the middleware' }
// state after dispatch: [ 'Use Redux', 'Understand the middleware' ]

logger是通用的中間件格式,這是一個三層嵌套函數,分別是{getState, dispatch}, next(實際上是下一個包裝後的中間件)和action入參,其實至關於

function middleware ({getState, dispatch}) {
  return (next) => {
    return (action) => {
      // dosomething
      // 調用 middleware 鏈中下一個 middleware 的 dispatch。
      let returnValue = next(action)
      // dosomething
      // 通常會是 action 自己,除非
      // 後面的 middleware 修改了它。
      return returnValue
    }
  }
}

知道這個基本規則以後咱們就能夠看看applyMiddleware裏面作了什麼

咱們看一下先過一下源碼裏面作了些什麼

import compose from './compose'
import { Middleware, MiddlewareAPI } from './types/middleware'
import { AnyAction } from './types/actions'
import { StoreEnhancer, StoreCreator, Dispatch } from './types/store'
import { Reducer } from './types/reducers'

/**
 * 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 middlewares The middleware chain to be applied.
 * @returns A store enhancer applying the middleware.
 *
 * @template Ext Dispatch signature added by a middleware.
 * @template S The type of the state supported by a middleware.
 */

總的來講就是建立一個應用程序的中間件去加強redux store的dispatch方法,對多種類的任務來講很是便利,例如以簡潔的方式表達異步流程或者輸出每一個action payload的日誌,而每一箇中間件都會拿到dispatchgetState入參

-------------省略部分代碼----------------
export default function applyMiddleware(
  ...middlewares: Middleware[]
): StoreEnhancer<any> {
  return (createStore: StoreCreator) => <S, A extends AnyAction>(
    reducer: Reducer<S, A>,
    ...args: any[]
  ) => {
    const store = createStore(reducer, ...args)
    let dispatch: Dispatch = () => {
      throw new Error(
        'Dispatching while constructing your middleware is not allowed. ' +
          'Other middleware would not be applied to this dispatch.'
      )
    }

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

    return {
      ...store,
      dispatch
    }
  }
}

大體分析一下代碼裏作了什麼操做

  1. 接收多箇中間件入參
  2. 接收createStore函數
  3. 接收reducer和其餘入參
  4. 用上面的參數實例化新的store
  5. 定義dispatch,拋出異常'不容許在構建中間件的時候dispatch,由於其餘中間件不會被應用到該次dispatch'
  6. 構建middlewareAPI對象,暴露出對應的方法,目的是讓每一個執行中間件都是同樣的入參條件
  7. 遍歷中間件返回執行middlewareAPI以後的新函數數組
  8. 從新賦值dispatch函數爲compose以後的返回值
  9. 最終拋出store實例的屬性方法和包裝後的新dispatch方法

applyMiddleware.ts源碼地址

上面有一個沒解析的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 funcs The functions to compose.
 * @returns 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: Function[]) {
  if (funcs.length === 0) {
    // infer the argument type so it is usable in inference down the line
    return <T>(arg: T) => arg
  }

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

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

總的來講,除了類型判斷,實際代碼只有一個reduce的應用...,這裏能夠知道每一箇中間件是有順序關係的,因此應用的時候須要注意一下.

compose.ts源碼地址

applyMiddleware的相關源碼已通過了一遍,剩下咱們回顧一下在createStore裏是怎麼處理相關邏輯的,放心,真的很少

-------------省略部分代碼----------------
if (typeof enhancer !== 'undefined') {
  if (typeof enhancer !== 'function') {
    throw new Error('Expected the enhancer to be a function.')
  }

  return enhancer(createStore)(reducer, preloadedState as PreloadedState<
    S
  >) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
}
-------------省略部分代碼----------------

檢查到傳入enhancer的時候直接中斷流程,返回執行結果,

咱們再從新梳理一下流程:

調用方式

createStore(reducer, preloadedState, applyMiddleware(f1, f2, ...fn))

在createStore裏若是檢測到enhancer入參會

return enhancer(createStore)(reducer, preloadedState)
至關於
return applyMiddleware(f1, f2, ...fn)(createStore)(reducer, preloadedState)

在applyMiddleware源碼可得知

// 初始化一個store
let store = createStore(reducer, preloadedState);

// 每一箇中間件拿到的入參
const middlewareAPI: MiddlewareAPI = {
  getState: store.getState,
  dispatch: (action, ...args) => dispatch(action, ...args)
}

// 遍歷的中間件大概流程這樣子
middlewares = [
    f1(middlewareAPI) => s1(next) => t1(...arg)
    f2(middlewareAPI) => s2(next) => t2(...arg)
    fn(middlewareAPI) => sn(next) => tn(...arg)
]

// chain獲得的數組就長這樣子
const chain = [
    s1(next) => t1(...arg)
    s2(next) => t2(...arg)
    sn(next) => tn(...arg)
]

// compose通過reduce方法包裝返回
const composeFn = s1((s2(sn(next) => tn(...arg))()) => t2(...arg))()) => t1(...arg)

// 最終返回的dispatch方法
dispatch = (composeFn)(store.dispatch)

// 總體流程
const applyMiddleware = (中間件數組) => (createStore) => (reducer, preloadedState) => {...store, dispatch: compose(...chain)(store.dispatch)}

這時候再回到中間件的通用代碼能夠知道

function middleware ({getState, dispatch}) {
  return (next) => {
    return (action) => {
      // dosomething
      // 調用 middleware 鏈中下一個 middleware 的 dispatch。
      let returnValue = next(action)
      // dosomething
      // 通常會是 action 自己,除非
      // 後面的 middleware 修改了它。
      return returnValue
    }
  }
}

階段一: 接收相同的初始{getState, dispatch}

階段二: 接收通過下一個中間件包裝後的dispatch調用

階段三: 接收action處理某些邏輯以後原樣返回,通常不應修改action

效果: dispatch一個action,會用倒序的方式逐一通過每一箇中間件的流程造成鏈式調用,而且先後通常不須要關心作些什麼操做.

applyMiddleware簡單實現

咱們既然已經知道了它的實現思路,接下來就能夠簡單封裝一個了

compose.js

function compose (...funcs) {
  if (funcs.length === 0) {
    // infer the argument type so it is usable in inference down the line
    return arg
  }

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

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

applyMiddleware.js

// 接收中間件數組
function applyMiddleware (...middlewares) {
  // 接收createStore函數和reducer和其餘參數
  return (createStore) => (reducer, ...args) => {
    // 這就是原始的實例化store,因此applyMiddleware方法其實就是圍繞在原始store的基礎上添加功能
    const store = createStore(reducer, ...args)

    // 先初始化dispatch方法佔位,可是此時執行會拋出異常
    let dispatch = () => {
      throw new Error(
        'Dispatching while constructing your middleware is not allowed. ' +
          'Other middleware would not be applied to this dispatch.'
      )
    }

    /**
     * 構建中間件第一層運行的入參對象, 保證每一箇中間件都是同樣的參數條件,因此上面的拋出異常也是如此
     * applyMiddleware([
        f1(middlewareAPI) => s1(next) => t1(...arg)
        fn(middlewareAPI) => sn(next) => tn(...arg)
     * ])
     *
     */
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (action, ...args) => dispatch(action, ...args)
    }

    // 遍歷運行每一箇中間件返回新的數組
    // chain = [s1(next) => t1(...arg), ...sn(next) => tn(...arg)]
    const chain = middlewares.map((middleware) => middleware(middlewareAPI))

    /* 返回加強功能後的dispatch方法
    dispatch = (s1(sn(next) => tn(...arg))()) => t1(...arg))(store.dispatch) */
    dispatch = compose(...chain)(store.dispatch)

    // 替代原始的store對象
    return {
      ...store,
      dispatch
    }
  }
}

由於新增了加強功能,因此咱們也要把createStore修改一下,按照源碼對應一下

由於參數裏只有reducer是必選,其餘二者都是可選,因此咱們還要把入參順序也替換一下

createStore.js

function createStore (reducer, initStore = {}, enhancer) {
  // 處理一下參數問題
  if (typeof initStore === 'function' && typeof enhancer === 'undefined') {
    enhancer = initStore
    initStore = undefined
  }

  // 劫持enhancer
  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }
    // 返回包裝後的store
    return enhancer(createStore)(reducer, initStore)
  }
  -------------省略部分代碼----------------
  return {
    getState,
    dispatch,
    subscribe
  }
}

最後只剩執行函數

store.js

// 初始數據
const initStore = {
  arNum: 0,
  mdNum: 1
}

// 日誌中間件
function logger ({ getState }) {
  return (next) => (action) => {
    console.log('will dispatch', action)

    // 調用 middleware 鏈中下一個 middleware 的 dispatch。
    let returnValue = next(action)

    console.log('state after dispatch', getState())

    // 通常會是 action 自己,除非
    // 後面的 middleware 修改了它。
    return returnValue
  }
}

// 實例化store
let store = createStore(reducers, initStore, applyMiddleware(logger))

如今在index.html引入新的依賴執行能夠發現也能正常輸出日誌了.

文章的完整代碼能夠直接查看demo7

bindActionCreators源碼解析

其實上面就已經算是完成了一個簡單的狀態管理器了,可是咱們從Redux的API裏其實可以看到還有一個方法是咱們還沒了解過的,就大概說說

把一個 value 爲不一樣 action creator 的對象,轉成擁有同名 key 的對象。同時使用 dispatch 對每一個 action creator 進行包裝,以即可以直接調用它們。

唯一會使用到 bindActionCreators 的場景是當你須要把 action creator 往下傳到一個組件上,卻不想讓這個組件覺察到 Redux 的存在,並且不但願把 dispatch 或 Redux store 傳給它。

至於什麼情景會遇到須要使用

你或許要問:爲何不直接把 action creator 綁定到 store 實例上,就像傳統的 Flux 那樣?問題在於,這對於須要在服務端進行渲染的同構應用會有問題。多數狀況下,你的每一個請求都須要一個獨立的 store 實例,這樣你能夠爲它們提供不一樣的數據,可是在定義的時候綁定 action creator,你就只能使用一個惟一的 store 實例來對應全部請求了。

省略掉類型判斷後的源碼

import { Dispatch } from './types/store'
import {
  AnyAction,
  ActionCreator,
  ActionCreatorsMapObject
} from './types/actions'

function bindActionCreator<A extends AnyAction = AnyAction>(
  actionCreator: ActionCreator<A>,
  dispatch: Dispatch
) {
  return function(this: any, ...args: any[]) {
    return dispatch(actionCreator.apply(this, args))
  }
}

/**
 * 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 an action creator as the first argument,
 * and get a dispatch wrapped function in return.
 *
 * @param 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 dispatch The `dispatch` function available on your Redux
 * store.
 *
 * @returns 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: ActionCreator<any> | ActionCreatorsMapObject,
  dispatch: Dispatch
) {
  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 boundActionCreators: ActionCreatorsMapObject = {}
  for (const key in actionCreators) {
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}

bindActionCreator返回綁定this指向的新函數

bindActionCreators作了三件事:

  1. 若是actionCreators是函數,直接返回調用bindActionCreator
  2. 若是actionCreators非對象非null拋出異常
  3. 若是actionCreators是可迭代對象,返回遍歷調用bindActionCreator包裝後的對象

由於咱們的demo不須要用到,就不必實現了,你們知道原理便可

bindActionCreators.ts源碼地址

相關文章
相關標籤/搜索