解析 Redux 源碼

也能夠看個人博客 - 解析 Redux 源碼javascript

解析 Redux 源碼

圖片描述

TIP

Redux 是 JavaScript 狀態容器,提供可預測化的狀態管理。html

做爲 React 全家桶的一份子,Redux 可謂說也是名聲響響,在 2016 年學習 JavaScript 想必沒有多少人沒聽過吧。java

這裏,本文不是來教你們如何使用 Redux 的 API 的,這一類的文章已經不少,對於 Redux 的介紹和學習能夠點擊下列連接:react

Redux 體小精悍(只有2kB)且沒有任何依賴,所以本文想經過閱讀 Redux 的源碼來學習 Redux 的使用以及思想。閉包

源碼結構

Redux 的源碼結構很簡單,咱們能夠直接看 src 目錄下的代碼:app

.src
├── utils                #工具函數
├── applyMiddleware.js
├── bindActionCreators.js        
├── combineReducers.js     
├── compose.js       
├── createStore.js  
└── index.js             #入口 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'

function isCrushed() {}

if (
  process.env.NODE_ENV !== 'production' &&
  typeof isCrushed.name === 'string' &&
  isCrushed.name !== 'isCrushed'
) {
  warning(
    '。。。'
  )
}

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

這裏的 isCrushed 函數主要是爲了驗證在非生產環境下 Redux 是否被壓縮(由於若是被壓縮了那麼 (isCrushed.name !== 'isCrushed') 就是 true),若是被壓縮會給開發者一個 warn 提示)。

而後就是暴露 createStore combineReducers bindActionCreators applyMiddleware compose 這幾個接口給開發者使用,咱們來逐一解析這幾個 API。

createStore.js

這個是 Redux 最主要的一個 API 了,它建立一個 Redux store 來以存放應用中全部的 state,應用中應有且僅有一個 store。

import isPlainObject from 'lodash/isPlainObject'
import $$observable from 'symbol-observable'

// 私有 action
export var ActionTypes = {
  INIT: '@@redux/INIT'
}

export default function createStore(reducer, preloadedState, enhancer) {
  // 判斷接受的參數個數,來指定 reducer 、 preloadedState 和 enhancer
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  // 若是 enhancer 存在而且適合合法的函數,那麼調用 enhancer,而且終止當前函數執行
  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, preloadedState)
  }

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

  // 儲存當前的 currentReducer
  var currentReducer = reducer
  // 儲存當前的狀態
  var currentState = preloadedState
  // 儲存當前的監聽函數列表
  var currentListeners = []
  // 儲存下一個監聽函數列表
  var nextListeners = currentListeners
  var isDispatching = false

  // 這個函數能夠根據當前監聽函數的列表生成新的下一個監聽函數列表引用
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

  ... getState ...

  ... subscribe ...

  ... dispatch ...

  ... replaceReducer ...

  ... observable ...

  dispatch({ type: ActionTypes.INIT })

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

首先定義了一個 ActionTypes 對象,它是一個 action,是一個 Redux 的私有 action,不容許外界觸發,用來初始化 Store 的狀態樹和改變 reducers 後初始化 Store 的狀態樹。

createStore

而後着重來看 createStore 函數:

接受

它能夠接受三個參數,reducer、preloadedState、enhancer:

  • reducer:是一個函數,返回下一個狀態,接受兩個參數:當前狀態 和 觸發的 action;

  • preloadedState:初始狀態對象,能夠很隨意指定,好比服務端渲染的初始狀態,可是若是使用 combineReducers 來生成 reducer,那必須保持狀態對象的 key 和 combineReducers 中的 key 相對應;

  • enhancer:store 的加強器函數,能夠指定爲 第三方的中間件,時間旅行,持久化 等等,可是這個函數只能用 Redux 提供的 applyMiddleware 函數來生成;

根據傳入參數的個數和類型,判斷 reducer 、 preloadedState 、 enhancer;

返回

調用完函數它返回的接口是 dispatch subscribe getState replaceReducer[$$observable]

這也是咱們開發中主要使用的幾個接口。

enhancer

若是 enhancer 參數傳入而且是個合法的函數,那麼就是調用 enhancer 函數(傳入 createStore 來給它操做),enhancer 函數返回的也是一個函數,在這裏傳入 reducerpreloadedState,而且返回函數調用結果,終止當前函數執行;
在 enhancer 函數裏面是如何操做使用的能夠看 applyMiddleware 部分;

getState
function getState() {
  return currentState
}

這個函數能夠獲取當前的狀態,createStore 中的 currentState 儲存當前的狀態樹,這是一個閉包,這個參數會持久存在,而且全部的操做狀態都是改變這個引用,getState 函數返回當前的 currentState

subscribe
function subscribe(listener) {
  // 判斷傳入的參數是否爲函數
  if (typeof listener !== 'function') {
    throw new Error('Expected listener to be a function.')
  }

  var isSubscribed = true

  ensureCanMutateNextListeners()
  nextListeners.push(listener)

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

    isSubscribed = false

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

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

dispatch
function dispatch(action) {
  if (!isPlainObject(action)) {
    throw new Error(
      'Actions must be plain objects. ' +
      'Use custom middleware for async actions.'
    )
  }

  // 判斷 action 是否有 type{必須} 屬性
  if (typeof action.type === 'undefined') {
    throw new Error(
      'Actions may not have an undefined "type" property. ' +
      'Have you misspelled a constant?'
    )
  }

  // 若是正在 dispatch 則拋出錯誤
  if (isDispatching) {
    throw new Error('Reducers may not dispatch actions.')
  }

  // 對拋出 error 的兼容,可是不管如何都會繼續執行 isDispatching = false 的操做
  try {
    isDispatching = true
    // 使用 currentReducer 來操做傳入 當前狀態和action,放回處理後的狀態
    currentState = currentReducer(currentState, action)
  } finally {
    isDispatching = false
  }

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

  return action
}

這個函數是用來觸發狀態改變的,他接受一個 action 對象做爲參數,而後 reducer 根據 action 的屬性 以及 當前 store 的狀態來生成一個新的狀態,賦予當前狀態,改變 store 的狀態;
currentState = currentReducer(currentState, action)
這裏的 currentReducer 是一個函數,他接受兩個參數:當前狀態 和 action,而後返回計算出來的新的狀態;
而後遍歷 nextListeners 列表,調用每一個監聽函數;

replaceReducer
function replaceReducer(nextReducer) {
  // 判斷參數是不是函數類型
  if (typeof nextReducer !== 'function') {
    throw new Error('Expected the nextReducer to be a function.')
  }

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

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

observable
function observable() {
  var outerSubscribe = subscribe
  return {
    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()
      var unsubscribe = outerSubscribe(observeState)
      return { unsubscribe }
    },

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

對於這個函數,是不直接暴露給開發者的,它提供了給其餘觀察者模式/響應式庫的交互操做,具體可看 https://github.com/zenparsing/es-observable

last

最後執行 dispatch({ type: ActionTypes.INIT }),用來根據 reducer 初始化 store 的狀態。

compose.js

compose 能夠接受一組函數參數,從右到左來組合多個函數,而後返回一個組合函數;

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

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

  const last = funcs[funcs.length - 1]
  const rest = funcs.slice(0, -1)
  return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
}

applyMiddleware.js

export default function applyMiddleware(...middlewares) {
  // 這個返回的函數就是 enhancer,接受 createStore 函數,再返回一個函數,接受的其實只有 reducer 和 preloadedState;
  return (createStore) => (reducer, preloadedState, enhancer) => {
    var store = createStore(reducer, preloadedState, enhancer)
    var dispatch = store.dispatch
    var chain = []

    // 暴漏 getState 和 dispatch 給 第三方中間價使用
    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }
    // 創造第三方中間件使用 middlewareAPI 後返回的函數組成的數組
    chain = middlewares.map(middleware => middleware(middlewareAPI))

    // 結合這一組函數 和 dispatch 組成的新的 dispatch,而後這個暴漏給用戶使用,而原有的 store.dispatch 是不變的,可是不暴漏
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

applyMiddleware 函數的做用是組合 多個 中間件等等,而後返回一個函數(enhancer

還記得在 createStore 中的一段嗎:

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(...)

而後再執行 enhancer(createStore) 繼續以後的操做;

這裏 enhancer(createStore) 等同於 (reducer, preloadedState, enhancer) => { ... }

而後再執行 enhancer(createStore)(reducer, preloadedState)

再回到 applyMiddleware ,這裏調用了 var store = createStore(reducer, preloadedState, enhancer)
如上所見,這裏執行的時候已經沒有 enhancer 參數了,所以會再次執行 createStore 函數的所有部分,而後獲得一個返回的實例 store

以後會生成一個新的 dispatch ,先保存下來原生的 dispatchvar dispatch = store.dispatch

var middlewareAPI = {
  getState: store.getState,
  dispatch: (action) => dispatch(action)
}

這一步是把 store 的 getStatedispatch 接口暴露給中間件來操做: chain = middlewares.map(middleware => middleware(middlewareAPI))

最後組合 所有中間件的返回值(函數)chainstore.dispatch,而後返回新的 dispatchdispatch = compose(...chain)(store.dispatch)

這裏的 dispatch 並非原有的 store 的,而是通過組合中間件以後新的 dispatch

最後返回暴露給用戶的接口:

return {
  ...store,
  dispatch
}

主要仍是 store 原有的接口,可是用新的 dispatch 替換了原有的;這個函數其實就是根據中間件和store的接口生成新的 dispatch 而後暴露給用戶。

combineReducers.js

這個函數能夠組合一組 reducers(對象) ,而後返回一個新的 reducer 函數給 createStore 使用。

它接受一組 reducers 組成的對象,對象的 key 是對應 value(reducer函數)的狀態名稱;

好比: { userInfo: getUserInfo, list: getList }

userInfo 是根據 getUserInfo 函數計算出來的;

那麼 store 裏面的 state 結構就會是: { userInfo: ..., list: ... }

export default function combineReducers(reducers) {

  // 根據 reducers 生成最終合法的 finalReducers:value 爲 函數
  var reducerKeys = Object.keys(reducers)
  var finalReducers = {}
  for (var i = 0; i < reducerKeys.length; i++) {
    var 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]
    }
  }

  var finalReducerKeys = Object.keys(finalReducers)

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

  // 驗證 reducer 是否合法
  var sanityError
  try {
    assertReducerSanity(finalReducers)
  } catch (e) {
    sanityError = e
  }

  // 返回最終生成的 reducer
  return function combination(state = {}, action) {
    if (sanityError) {
      throw sanityError
    }

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

    var hasChanged = false
    var nextState = {}
    for (var i = 0; i < finalReducerKeys.length; i++) {
      var key = finalReducerKeys[i]
      var reducer = finalReducers[key]
      var previousStateForKey = state[key]
      var nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === 'undefined') {
        var errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    // 遍歷一遍看是否改變,而後返回原有狀態值或者新的狀態值
    return hasChanged ? nextState : state
  }
}

最終返回一個 combination 也就是真正傳入 createStore 的 reducer 函數;

這是一個標準的 reducer 函數,接受一個初始化狀態 和 一個 action 參數;

每次調用的時候會去遍歷 finalReducer (有效的 reducer 列表),獲取列表中每一個 reducer 對應的先前狀態: var previousStateForKey = state[key]
看到這裏就應該明白傳入的 reducers 組合爲何 key 要和 store 裏面的 state 的 key 相對應;

而後獲得當前遍歷項的下一個狀態: var nextStateForKey = reducer(previousStateForKey, action)
而後把它添加到總體的下一個狀態: nextState[key] = nextStateForKey

每次遍歷會判斷總體狀態是否改變: hasChanged = hasChanged || nextStateForKey !== previousStateForKey

在最後,若是沒有改變就返回原有狀態,若是改變了就返回新生成的狀態對象: return hasChanged ? nextState : state

bindActionCreators.js

bindActionCreators 函數能夠生成直接觸發 action 的函數;

實質上它只說作了這麼一個操做 bindActionFoo = (...args) => dispatch(actionCreator(...args))

所以咱們直接調用 bindActionFoo 函數就能夠改變狀態了;

接受兩個參數,一個是 actionCreators( actionCreator 組成的對象,key 對於生成的函數名/或者是一個 actionCreator ),一個是 dispatch, store 實例中獲取;

function bindActionCreator(actionCreator, dispatch) {
  return (...args) => dispatch(actionCreator(...args))
}

export default function bindActionCreators(actionCreators, dispatch) {
  // 是一個函數,直接返回一個 bindActionCreator 函數,這個函數調用 dispatch 觸發 action
  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"?`
    )
  }

  // 遍歷對象,而後對每一個遍歷項的 actionCreator 生成函數,將函數按照原來的 key 值放到一個對象中,最後返回這個對象
  var keys = Object.keys(actionCreators)
  var boundActionCreators = {}
  for (var i = 0; i < keys.length; i++) {
    var key = keys[i]
    var actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}

總結

閱讀了一遍 Redux 的源碼,實在是太精妙了,少依賴,少耦合,純函數式。

相關文章
相關標籤/搜索