redux 源碼閱讀

【目錄結構】

image-20190827201059192

Redux 源碼能夠在任意項目中的 node_modules 文件夾下的 redux 中找到。咱們閱讀學習中主要關注 src 便可。java

src 下主要分紅兩個部分, 一部分是 utils 工具庫, 一部分是 redux 邏輯代碼。node

【utils】

Redux 自定義的工具庫react

下屬對應三個文件webpack

  • actionTypes.js
  • isPlainObject.js
  • warning.js

actionTypes.js

源碼以下:git

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 類型,比較好理解。github

其中的 randomString 方法用來獲取指定長度的隨機字符串, 這裏有個不少同窗都會忽略掉的知識點, Number.prototype.toString 方法 接受一個可選參數 radix ,該參數表明數字的基數, 也就是咱們常數的二進制、八進制等, 默認爲 10, 範圍在 2~36之間。web

isPlainObject.js

源碼以下:redux

export default function isPlainObject(obj) {
  
  if (typeof obj !== 'object' || obj === null) return false
  
  let proto = obj
  
  while (Object.getPrototypeOf(proto) !== null) {
    proto = Object.getPrototypeOf(proto)
  }
  
  return Object.getPrototypeOf(obj) === proto
}

這個文件對外暴露一個用來判斷是否爲簡單對象的方法。segmentfault

簡單對象

凡不是new Object()或者字面量的方式構建出來的對象都不是簡單對象

就是該對象的 __proto__ 等於 Object.prototype

舉🌰, 像 正則, 日期,類, 函數, 都不是簡單對象

具體的相關知識點,能夠去看下原型鏈的相關知識。

warning.js

源碼以下:

export default function warning(message) {
  
  if (typeof console !== 'undefined' && typeof console.error === 'function') {
    console.error(message)
  }
  
  try {
    throw new Error(message)
  } catch (e) {}
}

這個文件也是很簡單只是用來打印一下錯誤信息, 這裏對 console.error 加了層判斷處理, 用於處理兼容性問題。緣由是 ie8及其如下都是不支持 console

【邏輯代碼】

下屬文件以下:

  • applyMiddleware.js
  • bindActionCreators.js
  • combineReducers.js
  • compose.js
  • createStore.js
  • index.js

咱們首先從入口文件開始閱讀:

index.js

源碼以下:

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'
import __DO_NOT_USE__ActionTypes from './utils/actionTypes'

function 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 loose-envify (https://github.com/zertosh/loose-envify) for browserify ' +
      'or setting mode to production in webpack (https://webpack.js.org/concepts/mode/) ' +
      'to ensure you have the correct code for your production build.'
  )
}

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

這裏的入口文件主要的工做是把其他的幾個文件作一個統一導出, 須要關注的地方有兩點

  1. __DO_NOT_USE__ActionTypes 。 經過導入路徑能夠得知,引用自以前 untils 中的 actionTypes.js 中,平時工做中也用不到,官方文檔也未作解釋,暫且放下無論,之後再研究 。。。

  2. 函數 isCrushed 從字面理解,這是函數是用來判斷是不是碎的, 但函數體中卻什麼都沒有,非常奇怪,帶着這個疑問往下看這個 if 語句,在這裏給出了答案,

    1. process.env.NODE_ENV !== 'production'
    2. typeof isCrushed.name === 'string'
    3. isCrushed.name !== 'isCrushed'

    這三個條件從字面上也很是好理解。 當三個條件都不知足時 執行 warning 拋出異常信息。信息內容翻譯以下:

    「您目前正在使用NODE_ENV =''production'以外的縮小代碼。」+
    '這意味着你正在運行一個較慢的Redux開發版本。 '+
    '你可使用loose-envify(https://github.com/zertosh/loose-envify)進行瀏覽器化'+
    '或者爲Webpack定義插件(http://stackoverflow.com/questions/30030031)'+
    '以確保您擁有正確的生產構建代碼。

    可能有同窗有對於前兩個條件還好理解, 對第三個條件會感到有些疑惑, 以爲沒有必要。這裏瞭解過些打包原理的同窗應該對這個判斷仍是比較好理解的。

    create-react-app 爲例: 項目代碼在生產環境下會對代碼內容進行一個深度壓縮,會將全部的變量名替換成 a, b, c 之類的字母, 因此當進行生成環境編譯後 函數 isCrushed 能夠就變成了 函數 i

    這個函數的主要做用就是防止開發者在開發環境下對代碼進行壓縮, 影響調試

createStore.js

源碼以下:

import $$observable from 'symbol-observable'

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

export default function createStore(reducer, preloadedState, enhancer) {
  
  // 參數處理
  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
    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.')
  }
    
  // 定義變量
  let currentReducer = reducer
  let currentState = preloadedState
  let currentListeners = []
  let nextListeners = currentListeners
  let isDispatching = false

  // 定義方法
  function ensureCanMutateNextListeners() {
    ...
  }

  function getState() {
    ...
  }

  function subscribe(listener) {
    ...
  }

  function dispatch(action) {
    ...
  }

  function replaceReducer(nextReducer) {
    ...
  }
    
  dispatch({ type: ActionTypes.INIT })

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

函數 createStore 建立一個 Redux store 來以儲存應用中全部的 state。應用用應有且僅有一個 store。

createStore(reducer, [preloadedState], enhancer)

返回的接口分別是 dispathc subscribe getState replaceReducer[$$observable]

參數

  1. reducer: function 接收兩個參數, 分別是當前的 state 樹和要處理的 action, 返回新的 state 樹。

  2. [preloadedState]: any 初始時的 state, 若是隻有兩個參數,而且第二個參數是個函數時,將此參數賦值爲第三個參數, 並忽略此參數

    因此咱們在使用時能夠不傳第二個參數,直接將第三個參數書寫到第二個參數的位置 一樣能夠正常使用

  3. enhancer: func enhaner 加強器 是一個組合 stroe creator 的高階函數,返回一個新的強化後的 store creator。 一般能夠理解爲中間件,同時它也容許你經過複合函數改變 store 接口。

    注意: 當存在多個參數時,且從第三個參數開始類型同爲 函數, createStroe 將默認理解你想要使用多個 加強器, 這裏會拋出異常提示你須要將多個加強器合併成一個函數傳入。

    常見的 enhancer 有 redux-thunk 以及 redux-sage。通常都會配合 applyMiddleware 一塊兒使用, 其做用就是將這些 enhancer 格式化成符合 redux 要求的 enhancer。

    舉個🌰:

    import {createStore, applyMiddleware} from 'redux'
    import thunk from 'redux-thunk'
    import logger from 'redux-logger'
    
    import reducer from './reducers'
    
    const store = createStore(reducer, applyMiddleware(thunk))
    
    export default store

變量

let currentState = preloadedState //從函數createStore第二個參數preloadedState得到
let currentReducer = reducer  //從函數createStore第一個參數reducer得到
let currentListeners = [] //當前訂閱者列表
let nextListeners = currentListeners //新的訂閱者列表
let isDispatching = false

其中的 isDispatching 是做爲鎖來用的, 咱們都知道 redux 是一個統一的狀態管理容器。因此同一時間裏咱們必須保證只要一個action在執行

ensureCanMutateNextListeners

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

確保 nextListeners 與 currentListeners 不存在同一個引用關係, 主要服務於 dispatch 與訂閱。

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
}

這裏咱們能夠看到 dispatch 方法中作了三重判斷,

1. action 必須是個簡單函數; 
2. action.type 存在;
3.  未鎖住狀態。

只有當三重判斷都經過時, 才往下執行 action 並進行上鎖

getState

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
}

getState 很是簡單, 返回 currentState 便可。這個 currentState 在每次 dispatch 的時候都會進行更新, 同dipatch 同樣在進行 reducer 操做的時候, 是不能夠讀取到當前 state 裏的值的。

提問: 執行 createStore 函數生成 store, 可不能夠直接修改它的 state?

答案是: 能夠的, 但redux 不容許這麼去作, 由於這樣不會通知到訂閱者從新更新數據。

subscribe

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

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

    isSubscribed = false

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

這個函數能夠給 store 的狀態添加訂閱監聽函數,一旦調用 dispatch ,全部的監聽函數就會執行;
nextListeners 就是儲存當前監聽函數的列表,調用 subscribe,傳入一個函數做爲參數,那麼就會給 nextListeners 列表 push 這個函數;
同時調用 subscribe 函數會返回一個 unsubscribe 函數,用來解綁當前傳入的函數,同時在 subscribe 函數定義了一個 isSubscribed 標誌變量來判斷當前的訂閱是否已經被解綁,解綁的操做就是從 nextListeners 列表中刪除當前的監聽函數。

replaceReducer

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

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

這個函數能夠替換 store 當前的 reducer 函數,首先直接把 currentReducer = nextReducer,直接替換;
而後 dispatch({ type: ActionTypes.INIT }) ,用來初始化替換後 reducer 生成的初始化狀態而且賦予 store 的狀態;平時很難用到。

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/proposal-observable
 */
function observable() {
  ...
}

這個不是暴露給咱們開發者的, 不須要掌握。所以貼給註釋,就不貼詳細代碼, 關於 函數 observable 有興趣的能夠去做者的 gayhub(github)去學習一下: https://github.com/tc39/proposal-observable

compose.js

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

這個函數的意義就是將多個函數方法合併組成一個函數並返回。 注意這裏的組合是從右像左進行組合。

applyMiddleware.js

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 須要結合到 createStore 中的第三個參數 enhancer 加強器。經過對比咱們能夠得出 applyMiddleware 經過組合多箇中間件返回的一個 enhaner 。

能夠看一下前面 createStore 中 enhaner 的相關代碼

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

  return enhancer(createStore)(reducer, preloadedState)
}

這裏的 enhancer === applyMiddleware(...)

。。。( 讀不動了 ,😳懵逼中,,, 理解不了 🤣🤣 , 跳過 跳過 😂😂)

bindActionCreators.js

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

export default function bindActionCreators(actionCreators, 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 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
}

這部分代碼很好理解, 返回一個可以直接觸發 action 的函數。

第一個參數類型應爲一個函數或者對象。 當爲對象時, 直接調用 bindActionCreator 方法進行返回。 當爲對象時, 對對象進行遍歷 將其中爲 類型爲 function 的 再次整合到一個對象中進行集中返回。

combineReducers.js

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

function getUndefinedStateErrorMessage(key, action) {
  ...
}

function getUnexpectedStateShapeWarningMessage(
  inputState,
  reducers,
  action,
  unexpectedKeyCache
) {
  ...
}

function assertReducerShape(reducers) {
  ...
}

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

這個js 主要用來將多個 reducer 合併成一個 進行統一返回, 工做中也比較常見。

工具方法:

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

    if (
      typeof reducer(undefined, {
        type: ActionTypes.PROBE_UNKNOWN_ACTION()
      }) === '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.`
      )
    }
  })
}

函數 assertReducerShape 將對傳入的 reducer 作了兩個判斷處理, 兩個判斷分別針對於 初始的 state 未定義, 與 傳入一個未知的 type 作異常處理。 經過這裏咱們就不難理解 爲何 reducer 中爲什麼必須返回一個初始的 state 與 在 switch 中的 default 返回 原有的 state。

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([a-z|A-Z]+)/)[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 (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.`
    )
  }
}

獲取意外狀態形狀警告消息, 用來定義返回一下異常信息的描述內容, 供 warning 調用, 條件有: 傳入的 reducer 不能爲空對象、state 必須爲一個簡單對象、意外的 reducers 緩存。

getUndefinedStateErrorMessage

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

獲取未定義的狀態錯誤消息

業務邏輯

  1. 第一步將傳入的 reducer 對象 作一層淺拷貝,再賦值到 finalReducers
  2. 經過 assertReducerShape 檢測 finalReducers 是否都有默認返回值
  3. 經過 變量 hasChanged 來表示 state 是否產生變化,遍歷reducers集合,將每一個reducer對應的原state傳入其中,得出其對應的新的state。緊接着後面對新的state作了一層未定義的校驗。 校驗後會與原 state 進行比對,發生變化則返回 nextState, 反之返回 原 state。

結束:

到這整個源碼算是粗略的讀了一遍,雖然代碼量很少, 只有區區數百行, 可是很繞的, 到如今有些地方依舊不能理解。不過對自身的幫助仍是很大, 這期間也拜讀了很多大佬的源碼剖析。在這也很感謝 有那麼多的大佬分享本身的學習心得體會。其中特別是 wuming 大佬的 redux源碼剖析 寫的十分精彩,有須要的同窗能夠前往拜讀一下。 這份源碼閱讀還存在不少理解不足, 隨着後續的理解進一步深刻,也會再次更新的。

相關文章
相關標籤/搜索