Redux 莞式教程 之 進階篇

Redux 進階教程

原文(保持更新):https://github.com/kenberkele...css

寫在前面

相信您已經看過 Redux 簡明教程,本教程是簡明教程的實戰化版本,伴隨源碼分析
Redux 用的是 ES6 編寫,看到有疑惑的地方的,能夠複製粘貼到這裏在線編譯 ES5html

§ Redux API 總覽

在 Redux 的源碼目錄 src/,咱們能夠看到以下文件結構:react

├── utils/
│     ├── warning.js # 打醬油的,負責在控制檯顯示警告信息
├── applyMiddleware.js
├── bindActionCreators.js
├── combineReducers.js
├── compose.js
├── createStore.js
├── index.js # 入口文件

除去打醬油的 utils/warning.js 以及入口文件 index.js,剩下那 5 個就是 Redux 的 APIgit

§ compose(...functions)

先說這個 API 的緣由是它沒有依賴,是一個純函數github

⊙ 源碼分析

/**
 * 看起來逼格很高,實際運用實際上是這樣子的:
 * compose(f, g, h)(...arg) => f(g(h(...args)))
 *
 * 值得注意的是,它用到了 reduceRight,所以執行順序是從右到左
 *
 * @param  {多個函數,用逗號隔開}
 * @return {函數}
 */

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

這裏的關鍵點在於,reduceRight 可傳入初始值:編程

// 因爲 reduce / reduceRight 僅僅是方向的不一樣,所以下面用 reduce 說明便可
var arr = [1, 2, 3, 4, 5]

var re1 = arr.reduce(function(total, i) {
  return total + i
})
console.log(re1) // 15

var re2 = arr.reduce(function(total, i) {
  return total + i
}, 100) // <---------------傳入一個初始值
console.log(re2) // 115

下面是 compose 的實例(在線演示):redux

<!DOCTYPE html>
<html>
<head>
  <script src="//cdn.bootcss.com/redux/3.5.2/redux.min.js"></script>
</head>
<body>
<script>
function func1(num) {
  console.log('func1 得到參數 ' + num);
  return num + 1;
}

function func2(num) {
  console.log('func2 得到參數 ' + num);
  return num + 2;
}
  
function func3(num) {
  console.log('func3 得到參數 ' + num);
  return num + 3;
}

// 有點難看(若是函數名再長一點,那屏幕就不夠寬了)
var re1 = func3(func2(func1(0)));
console.log('re1:' + re1);

console.log('===============');

// 很優雅
var re2 = Redux.compose(func3, func2, func1)(0);
console.log('re2:' + re2);
</script>
</body>
</html>

控制檯輸出:後端

func1 得到參數 0
func2 得到參數 1
func3 得到參數 3
re1:6
===============
func1 得到參數 0
func2 得到參數 1
func3 得到參數 3
re2:6

§ createStore(reducer, initialState, enhancer)

⊙ 源碼分析

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

/**
 * 這是 Redux 的私有 action 常量
 * 長得太醜了,你不要鳥就好了
 */
export var ActionTypes = {
  INIT: '@@redux/INIT'
}

/**
 * @param  {函數}  reducer 很少解釋了
 * @param  {對象}  preloadedState 主要用於先後端同構時的數據同步
 * @param  {函數}  enhancer 很牛逼,能夠實現中間件、時間旅行,持久化等
 * ※ Redux 僅提供 appleMiddleware 這個 Store Enhancer ※
 * @return {Store}
 */
export default function createStore(reducer, preloadedState, enhancer) {
  // 這裏省略的代碼,到本文的最後再講述(用於壓軸你懂的)
  
  var currentReducer = reducer
  var currentState = preloadedState //     這就是整個應用的 state
  var currentListeners = [] //             用於存儲訂閱的回調函數,dispatch 後逐個執行
  var nextListeners = currentListeners // 【懸念1:爲何須要兩個 存放回調函數 的變量?】
  var isDispatching = false

  /**
   * 【懸念1·解疑】
   * 試想,dispatch 後,回調函數正在乖乖地被逐個執行(for 循環進行時)
   * 假設回調函數隊列本來是這樣的 [a, b, c, d]
   *
   * 如今 for 循環執行到第 3 步,亦即 a、b 已經被執行,準備執行 c
   * 但在這電光火石的瞬間,a 被取消訂閱!!!
   *
   * 那麼此時回調函數隊列就變成了 [b, c, d]
   * 那麼第 3 步就對應換成了 d!!!
   * c 被跳過了!!!這就是躺槍。。。
   * 
   * 做爲一個回調函數,最大的恥辱就是得不到執行
   * 所以爲了不這個問題,本函數會在上述場景中把
   * currentListeners 複製給 nextListeners
   *
   * 這樣的話,dispatch 後,在逐個執行回調函數的過程當中
   * 若是有新增訂閱或取消訂閱,都在 nextListeners 中操做
   * 讓 currentListeners 中的回調函數得以完整地執行
   *
   * 既然新增是在 nextListeners 中 push,所以毫無疑問
   * 新的回調函數不會在本次 currentListeners 的循環體中被觸發
   *
   * (上述事件發生的概率雖然很低,但仍是嚴謹點比較好)
   */
  function ensureCanMutateNextListeners() { // <-------這貨就叫作【ensure 哥】吧
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

  /**
   * 返回 state
   */
  function getState() {
    return currentState
  }

  /**
   * 負責註冊回調函數的老司機
   * 
   * 這裏須要注意的就是,回調函數中若是須要獲取 state
   * 那每次獲取都請使用 getState(),而不是開頭用一個變量緩存住它
   * 由於回調函數執行期間,有可能有連續幾個 dispatch 讓 state 改得物是人非
   * 並且別忘了,dispatch 以後,整個 state 是被徹底替換掉的
   * 你緩存的 state 指向的可能已是老掉牙的 state 了!!!
   *
   * @param  {函數} 想要訂閱的回調函數
   * @return {函數} 取消訂閱的函數
   */
  function subscribe(listener) {
    if (typeof listener !== 'function') {
      throw new Error('Expected listener to be a function.')
    }

    var isSubscribed = true

    ensureCanMutateNextListeners() // 調用 ensure 哥保平安
    nextListeners.push(listener)   // 新增訂閱在 nextListeners 中操做

    // 返回一個取消訂閱的函數
    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }

      isSubscribed = false

      ensureCanMutateNextListeners() // 調用 ensure 哥保平安
      var index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1) // 取消訂閱仍是在 nextListeners 中操做
    }
  }

  /**
   * 改變應用狀態 state 的不二法門:dispatch 一個 action
   * 內部的實現是:往 reducer 中傳入 currentState 以及 action
   * 用其返回值替換 currentState,最後逐個觸發回調函數
   *
   * 若是 dispatch 的不是一個對象類型的 action(同步的),而是 Promise / thunk(異步的)
   * 則需引入 redux-thunk 等中間件來反轉控制權【懸念2:什麼是反轉控制權?】
   * 
   * @param & @return {對象} action
   */
  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 與 action 會流通到全部的 reducer
      // 全部 reducer 的返回值整合後,替換掉當前的 currentState
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    // 令 currentListeners 等於 nextListeners,表示正在逐個執行回調函數(這就是上面 ensure 哥的斷定條件)
    var listeners = currentListeners = nextListeners

    // 逐個觸發回調函數。這裏不緩存數組長度是明智的,緣由見【懸念1·解疑】
    for (var i = 0; i < listeners.length; i++) {
      listeners[i]()
    }

    return action // 爲了方便鏈式調用,dispatch 執行完畢後,返回 action(下文會提到的,稍微記住就行了)
  }

  /**
   * 替換當前 reducer 的老司機
   * 主要用於代碼分離按需加載、熱替換等狀況
   *
   * @param {函數} nextReducer
   */
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }

    currentReducer = nextReducer //         就是這麼簡單粗暴!
    dispatch({ type: ActionTypes.INIT }) // 觸發生成新的 state 樹
  }

  /**
   * 這是留給 可觀察/響應式庫 的接口(詳情 https://github.com/zenparsing/es-observable)
   * 若是您瞭解 RxJS 等響應式編程庫,那可能會用到這個接口,不然請略過
   * @return {observable}
   */
  function observable() {略}

  // 這裏 dispatch 只是爲了生成 應用初始狀態
  dispatch({ type: ActionTypes.INIT })

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

【懸念2:什麼是反轉控制權? · 解疑】
在同步場景下,dispatch(action) 的這個 action 中的數據是同步獲取的,並無控制權的切換問題
但異步場景下,則須要將 dispatch 傳入到回調函數。待異步操做完成後,回調函數自行調用 dispatch(action) api

說白了:在異步 Action Creator 中自行調用 dispatch 就至關於反轉控制權
您徹底能夠本身實現,也能夠藉助 redux-thunk / redux-promise 等中間件統一實現
(它們的做用也僅僅就是把 dispatch 等傳入異步 Action Creator 罷了)數組

拓展閱讀:阮老師的 Thunk 函數的含義與用法
題外話:您不以爲 JavaScript 的回調函數,就是反轉控制權最廣泛的體現嗎?

§ combineReducers(reducers)

⊙ 應用場景

簡明教程中的 code-7 以下:

/** 本代碼塊記爲 code-7 **/
var initState = {
  counter: 0,
  todos: []
}

function reducer(state, action) {
  if (!state) state = initState
  
  switch (action.type) {
    case 'ADD_TODO':
      var nextState = _.deepClone(state) // 用到了 lodash 的深克隆
      nextState.todos.push(action.payload) 
      return nextState

    default:
      return state
  }
}

上面的 reducer 僅僅是實現了 「新增待辦事項」 的 state 的處理
咱們還有計數器的功能,下面咱們繼續增長計數器 「增長 1」 的功能:

/** 本代碼塊記爲 code-8 **/
var initState = { counter: 0, todos: [] }

function reducer(state, action) {
  if (!state) return initState // 如果初始化可當即返回應用初始狀態
  var nextState = _.deepClone(state) // 不然二話不說先克隆
  
  switch (action.type) {
    case 'ADD_TODO': // 新增待辦事項
      nextState.todos.push(action.payload) 
      break   
    case 'INCREMENT': // 計數器加 1
      nextState.counter = nextState.counter + 1
      break
  }
  return nextState
}

若是說還有其餘的動做,都須要在 code-8 這個 reducer 中繼續堆砌處理邏輯
但咱們知道,計數器 與 待辦事項 屬於兩個不一樣的模塊,不該該都堆在一塊兒寫
若是以後又要引入新的模塊(例如留言板),該 reducer 會愈來愈臃腫
此時就是 combineReducers 大顯身手的時刻:

目錄結構以下
reducers/
   ├── index.js
   ├── counterReducer.js
   ├── todosReducer.js
/** 本代碼塊記爲 code-9 **/
/* reducers/index.js */
import { combineReducers } from 'redux'
import counterReducer from './counterReducer'
import todosReducer from './todosReducer'

const rootReducer = combineReducers({
  counter: counterReducer, // <-------- 鍵名就是該 reducer 對應管理的 state
  todos: todosReducer
})

export default rootReducer

-------------------------------------------------

/* reducers/counterReducer.js */
export default function counterReducer(counter = 0, action) { // 傳入的 state 實際上是 state.counter
  switch (action.type) {
    case 'INCREMENT':
      return counter + 1 // counter 是值傳遞,所以能夠直接返回一個值
    default:
      return counter
  }
}

-------------------------------------------------

/* reducers/todosReducers */
export default function todosReducer(todos = [], action) { // 傳入的 state 實際上是 state.todos
  switch (action.type) {
    case 'ADD_TODO':
      return [ ...todos, action.payload ]
    default:
      return todos
  }
}

code-8 reducercode-9 rootReducer 的功能是同樣的,但後者的各個子 reducer 僅維護對應的那部分 state
其可操做性、可維護性、可擴展性大大加強

Flux 中是根據不一樣的功能拆分出多個 store 分而治之
而 Redux 只容許應用中有惟一的 store,經過拆分出多個 reducer 分別管理對應的 state


下面繼續來深刻使用 combineReducers。一直以來咱們的應用狀態都是隻有兩層,以下所示:

state
  ├── counter: 0
  ├── todos: []

若是說如今又有一個需求:在待辦事項模塊中,存儲用戶每次操做(增刪改)的時間,那麼此時應用初始狀態樹應爲:

state
  ├── counter: 0
  ├── todo
        ├── optTime: []
        ├── todoList: [] # 這其實就是原來的 todos!

那麼對應的 reducer 就是:

目錄結構以下
reducers/
   ├── index.js <-------------- combineReducers (生成 rootReducer)
   ├── counterReducer.js
   ├── todoReducers/ <--------- combineReducers
           ├── index.js
           ├── optTimeReducer.js
           ├── todoListReducer.js
/* reducers/index.js */
import { combineReducers } from 'redux'
import counterReducer from './counterReducer'
import todoReducers from './todoReducers/'

const rootReducer = combineReducers({
  counter: counterReducer,
  todo: todoReducers
})

export default rootReducer

=================================================

/* reducers/todoReducers/index.js */
import { combineReducers } from 'redux'
import optTimeReducer from './optTimeReducer'
import todoListReducer from './todoListReducer'

const todoReducers = combineReducers({
  optTime: optTimeReducer,
  todoList: todoListReducer
})

export default todoReducers

-------------------------------------------------

/* reducers/todosReducers/optTimeReducer.js */
export default function optTimeReducer(optTime = [], action) {
  // 咦?這裏怎麼沒有 switch-case 分支?誰說 reducer 就必定包含 switch-case 分支的?
  return action.type.includes('TODO') ? [ ...optTime, new Date() ] : optTime
}

-------------------------------------------------

/* reducers/todosReducers/todoListReducer.js */
export default function todoListReducer(todoList = [], action) {
  switch (action.type) {
    case 'ADD_TODO':
      return [ ...todoList, action.payload ]
    default:
      return todoList
  }
}

不管您的應用狀態樹有多麼的複雜,均可以經過逐層下分管理對應部分的 state

counterReducer(counter, action) -------------------- counter
                              ↗                                                              ↘
rootReducer(state, action) —→∑     ↗ optTimeReducer(optTime, action) ------ optTime ↘         nextState
                              ↘—→∑                                                    todo  ↗
                                   ↘ todoListReducer(todoList,action) ----- todoList ↗


注:左側表示 dispatch 分發流,∑ 表示 combineReducers;右側表示各實體 reducer 的返回值,最後彙總整合成 nextState

看了上圖,您應該能直觀感覺到爲什麼取名爲 reducer 了吧?把 state 分而治之,極大減輕開發與維護的難度

不管是 dispatch 哪一個 action,都會流通全部的 reducer
表面上看來,這樣子很浪費性能,但 JavaScript 對於這種純函數的調用是很高效率的,所以請儘管放心
這也是爲什麼 reducer 必須返回其對應的 state 的緣由。不然整合狀態樹時,該 reducer 對應的鍵名就是 undefined

⊙ 源碼分析

僅截取關鍵部分,畢竟有很大一部分都是類型檢測警告

function combineReducers(reducers) {
  var reducerKeys = Object.keys(reducers)
  var finalReducers = {}
  
  for (var i = 0; i < reducerKeys.length; i++) {
    var key = reducerKeys[i]
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }

  var finalReducerKeys = Object.keys(finalReducers)

  // 返回合成後的 reducer
  return function combination(state = {}, action) {
    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]                       // 獲取當前子 state
      var nextStateForKey = reducer(previousStateForKey, action) // 執行各子 reducer 中獲取子 nextState
      nextState[key] = nextStateForKey                           // 將子 nextState 掛載到對應的鍵名
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}

在此個人註釋不多,由於代碼寫得實在是太過明瞭了,註釋反而影響閱讀
做者 Dan 用了大量的 for 循環,的確有點不夠優雅

§ bindActionCreators(actionCreators, dispatch)

這個 API 有點雞肋,它無非就是作了這件事情:dispatch(ActionCreator(XXX))

⊙ 源碼分析

/* 爲 Action Creator 加裝上自動 dispatch 技能 */
function bindActionCreator(actionCreator, dispatch) {
  return (...args) => dispatch(actionCreator(...args))
}

export default function bindActionCreators(actionCreators, dispatch) {
  // 省去一大坨類型判斷
  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') {
      // 逐個裝上自動 dispatch 技能
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}

⊙ 應用場景

簡明教程中的 code-5 以下:

<--! 本代碼塊記爲 code-5 -->
<input id="todoInput" type="text" />
<button id="btn">提交</button>

<script>
$('#btn').on('click', function() {
  var content = $('#todoInput').val() // 獲取輸入框的值
  var action = addTodo(content) // 執行 Action Creator 得到 action
  store.dispatch(action) // 手動顯式 dispatch 一個 action
})
</script>

咱們看到,調用 addTodo 這個 Action Creator 後獲得一個 action,以後又要手動 dispatch(action)
若是是隻有一個兩個 Action Creator 仍是能夠接受,但若是有不少個那就顯得有點重複了(其實我以爲不重複哈哈哈)
這個時候咱們就能夠利用 bindActionCreators 實現自動 dispatch

<input id="todoInput" type="text" />
<button id="btn">提交</button>

<script>
// 全局引入 Redux、jQuery,同時 store 是全局變量
var actionsCreators = Redux.bindActionCreators(
  { addTodo: addTodo },
  store.dispatch // 傳入 dispatch 函數
)

$('#btn').on('click', function() {
  var content = $('#todoInput').val()
  actionCreators.addTodo(content) // 它會自動 dispatch
})
</script>

綜上,這個 API 沒啥卵用,尤爲是異步場景下,基本用不上

§ applyMiddleware(...middlewares)

Redux 中文文檔 高級 · Middleware 有提到中間件的演化由來

首先要理解何謂 Middleware,何謂 Enhancer

⊙ Middleware

說白了,Redux 引入中間件機制,其實就是爲了在 dispatch 先後,統一「作愛作的事」。。。
諸如統一的日誌記錄、引入 thunk 統一處理異步 Action Creator 等都屬於中間件
下面是一個簡單的打印動做先後 state 的中間件:

/* 裝逼寫法 */
const printStateMiddleware = ({ getState }) => next => action => {
  console.log('state before dispatch', getState())
  
  let returnValue = next(action)

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

  return returnValue
}

-------------------------------------------------

/* 下降逼格寫法 */
function printStateMiddleware(middlewareAPI) { // 記爲【錨點-1】,中間件內可用的 API
  return function (dispatch) {                 // 記爲【錨點-2】,傳入原 dispatch 的引用
    return function (action) {
      console.log('state before dispatch', middlewareAPI.getState())
  
      var returnValue = dispatch(action) // 還記得嗎,dispatch 的返回值其實仍是 action
  
      console.log('state after dispatch', middlewareAPI.getState())

      return returnValue // 繼續傳給下一個中間件做爲參數 action
    }
  }
}

⊙ Store Enhancer

說白了,Store 加強器就是對生成的 store API 進行改造,這是它與中間件最大的區別(中間件不修改 store 的 API)
而改造 store 的 API 就要從它的締造者 createStore 入手。例如,Redux 的 API applyMiddleware 就是一個 Store 加強器:

import compose from './compose' // 這貨的做用其實就是 compose(f, g, h)(action) => f(g(h(action)))

/* 傳入一坨中間件 */
export default function applyMiddleware(...middlewares) {

  /* 傳入 createStore */
  return function(createStore) {
  
    /* 返回一個函數簽名跟 createStore 如出一轍的函數,亦即返回的是一個加強版的 createStore */
    return function(reducer, preloadedState, enhancer) {
    
      // 用原 createStore 先生成一個 store,其包含 getState / dispatch / subscribe / replaceReducer 四個 API
      var store = createStore(reducer, preloadedState, enhancer)
      
      var dispatch = store.dispatch // 指向原 dispatch
      var chain = [] // 存儲中間件的數組
  
      // 提供給中間件的 API(其實都是 store 的 API)
      var middlewareAPI = {
        getState: store.getState,
        dispatch: (action) => dispatch(action)
      }
      
      // 給中間件「裝上」 API,見上面 ⊙Middleware【下降逼格寫法】的【錨點-1】 
      chain = middlewares.map(middleware => middleware(middlewareAPI))
      
      // 串聯各個中間件,爲各個中間件傳入原 store.dispatch,見【下降逼格寫法】的【錨點-2】
      dispatch = compose(...chain)(store.dispatch)
  
      return {
        ...store, // store 的 API 中保留 getState / subsribe / replaceReducer
        dispatch  // 新 dispatch 覆蓋原 dispatch,日後調用 dispatch 就會觸發 chain 內的中間件鏈式串聯執行
      }
    }
  }
}

最終返回的雖然仍是 store 的那四個 API,但其中的 dispatch 函數的功能被加強了,這就是所謂的 Store Enhancer

⊙ 綜合應用 ( 在線演示 )

<!DOCTYPE html>
<html>
<head>
  <script src="//cdn.bootcss.com/redux/3.5.2/redux.min.js"></script>
</head>
<body>
<script>
/** Action Creators */
function inc() {
  return { type: 'INCREMENT' };
}
function dec() {
  return { type: 'DECREMENT' };
}

function reducer(state, action) {
  state = state || { counter: 0 };

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

function printStateMiddleware(middlewareAPI) {
  return function (dispatch) {
    return function (action) {
      console.log('dispatch 前:', middlewareAPI.getState());
      var returnValue = dispatch(action);
      console.log('dispatch 後:', middlewareAPI.getState(), '\n');
      return returnValue;
    };
  };
}

var enhancedCreateStore = Redux.applyMiddleware(printStateMiddleware)(Redux.createStore);
var store = enhancedCreateStore(reducer);

store.dispatch(inc());
store.dispatch(inc());
store.dispatch(dec());
</script>
</body>
</html>

控制檯輸出:

dispatch 前:{ counter: 0 }
dispatch 後:{ counter: 1 }

dispatch 前:{ counter: 1 }
dispatch 後:{ counter: 2 }

dispatch 前:{ counter: 2 }
dispatch 後:{ counter: 1 }

實際上,上面生成 store 的代碼能夠更加優雅:

/** 本代碼塊記爲 code-10 **/
var store = Redux.createStore(
  reducer,
  Redux.applyMiddleware(printStateMiddleware)
)

若是有多箇中間件以及多個加強器,還能夠這樣寫(請留意序號順序):

重溫一下 createStore 完整的函數簽名:function createStore(reducer, preloadedState, enhancer)

/** 本代碼塊記爲 code-11 **/
import { createStore, applyMiddleware, compose } from 'redux'

const store = createStore(
  reducer,
  preloadedState, // <----- 可選,先後端同構的數據同步
  compose( // <------------ 還記得嗎?compose 是從右到左的哦!
    applyMiddleware( // <-- 這貨也是 Store Enhancer 哦!但這是關乎中間件的加強器,必須置於 compose 執行鏈的最後
      middleware1,
      middleware2,
      middleware3
    ),
    enhancer3,
    enhancer2,
    enhancer1
  )
)

爲何會支持那麼多種寫法呢?在 createStore 的源碼分析的開頭部分,我省略了一些代碼,如今奉上該壓軸部分:

/** 本代碼塊記爲 code-12 **/
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
  // 這裏就是上面 code-10 的狀況,只傳入 reducer 和 Store Enhancer 這兩個參數
  enhancer = preloadedState
  preloadedState = undefined
}

if (typeof enhancer !== 'undefined') {
  if (typeof enhancer !== 'function') {
    throw new Error('Expected the enhancer to be a function.')
  }
  // 存在 enhancer 就當即執行,返回加強版的 createStore <--------- 記爲【錨點 12-1】
  return enhancer(createStore)(reducer, preloadedState)
}

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

// 除 compose 外,createStore 居然也在此爲咱們提供了書寫的便利與自由度,實在是太體貼了

若是像 code-11 那樣有多個 enhancer,則 code-12 【錨點 12-1】 中的代碼會執行屢次
生成最終的超級加強版 store。最後,奉上 code-11compose 內部的執行順序示意圖:

原 createStore ————
                  │
                  ↓
return enhancer1(createStore)(reducer, preloadedState, enhancer2)
   |
   ├———————→ createStore 加強版 1
                    │
                    ↓
return enhancer2(createStore1)(reducer, preloadedState, enhancer3)
   |
   ├———————————→ createStore 加強版 1+2
                        │
                        ↓
return enhancer3(createStore1+2)(reducer, preloadedState, applyMiddleware(m1,m2,m3))
   |
   ├————————————————————→ createStore 加強版 1+2+3
                                     │
                                     ↓
return appleMiddleware(m1,m2,m3)(createStore1+2+3)(reducer, preloadedState)
   |
   ├——————————————————————————————————→ 生成最終加強版 store

§ 總結

Redux 有五個 API,分別是:

  • createStore(reducer, [initialState])

  • combineReducers(reducers)

  • applyMiddleware(...middlewares)

  • bindActionCreators(actionCreators, dispatch)

  • compose(...functions)

createStore 生成的 store 有四個 API,分別是:

  • getState()

  • dispatch(action)

  • subscribe(listener)

  • replaceReducer(nextReducer)

至此,若您已經理解上述 API 的做用機理,以及中間件與加強器的概念/區別
本人將不勝榮幸,不妨點個 star 算是對個人讚揚
如您對本教程有任何意見或改進的建議,歡迎 issue,我會盡快予您答覆

最後奉上 React + Redux + React Router 的簡易留言板實例:react-demo

拓展閱讀:中間件的洋蔥模型

相關文章
相關標籤/搜索