成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Redux 莞式教程 之 進(jìn)階篇

岳光 / 1875人閱讀

摘要:進(jìn)階教程原文保持更新寫在前面相信您已經(jīng)看過(guò)簡(jiǎn)明教程,本教程是簡(jiǎn)明教程的實(shí)戰(zhàn)化版本,伴隨源碼分析用的是編寫,看到有疑惑的地方的,可以復(fù)制粘貼到這里在線編譯總覽在的源碼目錄,我們可以看到如下文件結(jié)構(gòu)打醬油的,負(fù)責(zé)在控制臺(tái)顯示警告信息入口文件除去

Redux 進(jìn)階教程

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

寫在前面

相信您已經(jīng)看過(guò) Redux 簡(jiǎn)明教程,本教程是簡(jiǎn)明教程的實(shí)戰(zhàn)化版本,伴隨源碼分析
Redux 用的是 ES6 編寫,看到有疑惑的地方的,可以復(fù)制粘貼到這里在線編譯 ES5

§ Redux API 總覽

在 Redux 的源碼目錄 src/,我們可以看到如下文件結(jié)構(gòu):

├── utils/
│     ├── warning.js # 打醬油的,負(fù)責(zé)在控制臺(tái)顯示警告信息
├── applyMiddleware.js
├── bindActionCreators.js
├── combineReducers.js
├── compose.js
├── createStore.js
├── index.js # 入口文件

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

§ compose(...functions)

先說(shuō)這個(gè) API 的原因是它沒(méi)有依賴,是一個(gè)純函數(shù)

⊙ 源碼分析
/**
 * 看起來(lái)逼格很高,實(shí)際運(yùn)用其實(shí)是這樣子的:
 * compose(f, g, h)(...arg) => f(g(h(...args)))
 *
 * 值得注意的是,它用到了 reduceRight,因此執(zhí)行順序是從右到左
 *
 * @param  {多個(gè)函數(shù),用逗號(hào)隔開}
 * @return {函數(shù)}
 */

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

這里的關(guān)鍵點(diǎn)在于,reduceRight 可傳入初始值:

// 由于 reduce / reduceRight 僅僅是方向的不同,因此下面用 reduce 說(shuō)明即可
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) // <---------------傳入一個(gè)初始值
console.log(re2) // 115

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




  




控制臺(tái)輸出:

func1 獲得參數(shù) 0
func2 獲得參數(shù) 1
func3 獲得參數(shù) 3
re1:6
===============
func1 獲得參數(shù) 0
func2 獲得參數(shù) 1
func3 獲得參數(shù) 3
re2:6
§ createStore(reducer, initialState, enhancer) ⊙ 源碼分析
import isPlainObject from "lodash/isPlainObject"
import $$observable from "symbol-observable"

/**
 * 這是 Redux 的私有 action 常量
 * 長(zhǎng)得太丑了,你不要鳥就行了
 */
export var ActionTypes = {
  INIT: "@@redux/INIT"
}

/**
 * @param  {函數(shù)}  reducer 不多解釋了
 * @param  {對(duì)象}  preloadedState 主要用于前后端同構(gòu)時(shí)的數(shù)據(jù)同步
 * @param  {函數(shù)}  enhancer 很牛逼,可以實(shí)現(xiàn)中間件、時(shí)間旅行,持久化等
 * ※ Redux 僅提供 appleMiddleware 這個(gè) Store Enhancer ※
 * @return {Store}
 */
export default function createStore(reducer, preloadedState, enhancer) {
  // 這里省略的代碼,到本文的最后再講述(用于壓軸你懂的)
  
  var currentReducer = reducer
  var currentState = preloadedState //     這就是整個(gè)應(yīng)用的 state
  var currentListeners = [] //             用于存儲(chǔ)訂閱的回調(diào)函數(shù),dispatch 后逐個(gè)執(zhí)行
  var nextListeners = currentListeners // 【懸念1:為什么需要兩個(gè) 存放回調(diào)函數(shù) 的變量?】
  var isDispatching = false

  /**
   * 【懸念1·解疑】
   * 試想,dispatch 后,回調(diào)函數(shù)正在乖乖地被逐個(gè)執(zhí)行(for 循環(huán)進(jìn)行時(shí))
   * 假設(shè)回調(diào)函數(shù)隊(duì)列原本是這樣的 [a, b, c, d]
   *
   * 現(xiàn)在 for 循環(huán)執(zhí)行到第 3 步,亦即 a、b 已經(jīng)被執(zhí)行,準(zhǔn)備執(zhí)行 c
   * 但在這電光火石的瞬間,a 被取消訂閱?。?!
   *
   * 那么此時(shí)回調(diào)函數(shù)隊(duì)列就變成了 [b, c, d]
   * 那么第 3 步就對(duì)應(yīng)換成了 d!?。?   * c 被跳過(guò)了?。?!這就是躺槍。。。
   * 
   * 作為一個(gè)回調(diào)函數(shù),最大的恥辱就是得不到執(zhí)行
   * 因此為了避免這個(gè)問(wèn)題,本函數(shù)會(huì)在上述場(chǎng)景中把
   * currentListeners 復(fù)制給 nextListeners
   *
   * 這樣的話,dispatch 后,在逐個(gè)執(zhí)行回調(diào)函數(shù)的過(guò)程中
   * 如果有新增訂閱或取消訂閱,都在 nextListeners 中操作
   * 讓 currentListeners 中的回調(diào)函數(shù)得以完整地執(zhí)行
   *
   * 既然新增是在 nextListeners 中 push,因此毫無(wú)疑問(wèn)
   * 新的回調(diào)函數(shù)不會(huì)在本次 currentListeners 的循環(huán)體中被觸發(fā)
   *
   * (上述事件發(fā)生的幾率雖然很低,但還是嚴(yán)謹(jǐn)點(diǎn)比較好)
   */
  function ensureCanMutateNextListeners() { // <-------這貨就叫做【ensure 哥】吧
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

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

  /**
   * 負(fù)責(zé)注冊(cè)回調(diào)函數(shù)的老司機(jī)
   * 
   * 這里需要注意的就是,回調(diào)函數(shù)中如果需要獲取 state
   * 那每次獲取都請(qǐng)使用 getState(),而不是開頭用一個(gè)變量緩存住它
   * 因?yàn)榛卣{(diào)函數(shù)執(zhí)行期間,有可能有連續(xù)幾個(gè) dispatch 讓 state 改得物是人非
   * 而且別忘了,dispatch 之后,整個(gè) state 是被完全替換掉的
   * 你緩存的 state 指向的可能已經(jīng)是老掉牙的 state 了?。。?   *
   * @param  {函數(shù)} 想要訂閱的回調(diào)函數(shù)
   * @return {函數(shù)} 取消訂閱的函數(shù)
   */
  function subscribe(listener) {
    if (typeof listener !== "function") {
      throw new Error("Expected listener to be a function.")
    }

    var isSubscribed = true

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

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

      isSubscribed = false

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

  /**
   * 改變應(yīng)用狀態(tài) state 的不二法門:dispatch 一個(gè) action
   * 內(nèi)部的實(shí)現(xiàn)是:往 reducer 中傳入 currentState 以及 action
   * 用其返回值替換 currentState,最后逐個(gè)觸發(fā)回調(diào)函數(shù)
   *
   * 如果 dispatch 的不是一個(gè)對(duì)象類型的 action(同步的),而是 Promise / thunk(異步的)
   * 則需引入 redux-thunk 等中間件來(lái)反轉(zhuǎn)控制權(quán)【懸念2:什么是反轉(zhuǎn)控制權(quán)?】
   * 
   * @param & @return {對(duì)象} 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
      // 關(guān)鍵點(diǎn):currentState 與 action 會(huì)流通到所有的 reducer
      // 所有 reducer 的返回值整合后,替換掉當(dāng)前的 currentState
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    // 令 currentListeners 等于 nextListeners,表示正在逐個(gè)執(zhí)行回調(diào)函數(shù)(這就是上面 ensure 哥的判定條件)
    var listeners = currentListeners = nextListeners

    // 逐個(gè)觸發(fā)回調(diào)函數(shù)。這里不緩存數(shù)組長(zhǎng)度是明智的,原因見【懸念1·解疑】
    for (var i = 0; i < listeners.length; i++) {
      listeners[i]()
    }

    return action // 為了方便鏈?zhǔn)秸{(diào)用,dispatch 執(zhí)行完畢后,返回 action(下文會(huì)提到的,稍微記住就好了)
  }

  /**
   * 替換當(dāng)前 reducer 的老司機(jī)
   * 主要用于代碼分離按需加載、熱替換等情況
   *
   * @param {函數(shù)} nextReducer
   */
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== "function") {
      throw new Error("Expected the nextReducer to be a function.")
    }

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

  /**
   * 這是留給 可觀察/響應(yīng)式庫(kù) 的接口(詳情 https://github.com/zenparsing/es-observable)
   * 如果您了解 RxJS 等響應(yīng)式編程庫(kù),那可能會(huì)用到這個(gè)接口,否則請(qǐng)略過(guò)
   * @return {observable}
   */
  function observable() {略}

  // 這里 dispatch 只是為了生成 應(yīng)用初始狀態(tài)
  dispatch({ type: ActionTypes.INIT })

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

【懸念2:什么是反轉(zhuǎn)控制權(quán)? · 解疑】
在同步場(chǎng)景下,dispatch(action) 的這個(gè) action 中的數(shù)據(jù)是同步獲取的,并沒(méi)有控制權(quán)的切換問(wèn)題
但異步場(chǎng)景下,則需要將 dispatch 傳入到回調(diào)函數(shù)。待異步操作完成后,回調(diào)函數(shù)自行調(diào)用 dispatch(action)

說(shuō)白了:在異步 Action Creator 中自行調(diào)用 dispatch 就相當(dāng)于反轉(zhuǎn)控制權(quán)
您完全可以自己實(shí)現(xiàn),也可以借助 redux-thunk / redux-promise 等中間件統(tǒng)一實(shí)現(xiàn)
(它們的作用也僅僅就是把 dispatch 等傳入異步 Action Creator 罷了)

拓展閱讀:阮老師的 Thunk 函數(shù)的含義與用法
題外話:您不覺(jué)得 JavaScript 的回調(diào)函數(shù),就是反轉(zhuǎn)控制權(quán)最普遍的體現(xiàn)嗎?

§ combineReducers(reducers) ⊙ 應(yīng)用場(chǎng)景

簡(jiǎn)明教程中的 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 僅僅是實(shí)現(xiàn)了 “新增待辦事項(xiàng)” 的 state 的處理
我們還有計(jì)數(shù)器的功能,下面我們繼續(xù)增加計(jì)數(shù)器 “增加 1” 的功能:

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

function reducer(state, action) {
  if (!state) return initState // 若是初始化可立即返回應(yīng)用初始狀態(tài)
  var nextState = _.deepClone(state) // 否則二話不說(shuō)先克隆
  
  switch (action.type) {
    case "ADD_TODO": // 新增待辦事項(xiàng)
      nextState.todos.push(action.payload) 
      break   
    case "INCREMENT": // 計(jì)數(shù)器加 1
      nextState.counter = nextState.counter + 1
      break
  }
  return nextState
}

如果說(shuō)還有其他的動(dòng)作,都需要在 code-8 這個(gè) reducer 中繼續(xù)堆砌處理邏輯
但我們知道,計(jì)數(shù)器 與 待辦事項(xiàng) 屬于兩個(gè)不同的模塊,不應(yīng)該都堆在一起寫
如果之后又要引入新的模塊(例如留言板),該 reducer 會(huì)越來(lái)越臃腫
此時(shí)就是 combineReducers 大顯身手的時(shí)刻:

目錄結(jié)構(gòu)如下
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 對(duì)應(yīng)管理的 state
  todos: todosReducer
})

export default rootReducer

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

/* reducers/counterReducer.js */
export default function counterReducer(counter = 0, action) { // 傳入的 state 其實(shí)是 state.counter
  switch (action.type) {
    case "INCREMENT":
      return counter + 1 // counter 是值傳遞,因此可以直接返回一個(gè)值
    default:
      return counter
  }
}

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

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

code-8 reducercode-9 rootReducer 的功能是一樣的,但后者的各個(gè)子 reducer 僅維護(hù)對(duì)應(yīng)的那部分 state
其可操作性、可維護(hù)性、可擴(kuò)展性大大增強(qiáng)

Flux 中是根據(jù)不同的功能拆分出多個(gè) store 分而治之
而 Redux 只允許應(yīng)用中有唯一的 store,通過(guò)拆分出多個(gè) reducer 分別管理對(duì)應(yīng)的 state

下面繼續(xù)來(lái)深入使用 combineReducers。一直以來(lái)我們的應(yīng)用狀態(tài)都是只有兩層,如下所示:

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

如果說(shuō)現(xiàn)在又有一個(gè)需求:在待辦事項(xiàng)模塊中,存儲(chǔ)用戶每次操作(增刪改)的時(shí)間,那么此時(shí)應(yīng)用初始狀態(tài)樹應(yīng)為:

state
  ├── counter: 0
  ├── todo
        ├── optTime: []
        ├── todoList: [] # 這其實(shí)就是原來(lái)的 todos!

那么對(duì)應(yīng)的 reducer 就是:

目錄結(jié)構(gòu)如下
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) {
  // 咦?這里怎么沒(méi)有 switch-case 分支?誰(shuí)說(shuō) 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
  }
}

無(wú)論您的應(yīng)用狀態(tài)樹有多么的復(fù)雜,都可以通過(guò)逐層下分管理對(duì)應(yīng)部分的 state

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


注:左側(cè)表示 dispatch 分發(fā)流,∑ 表示 combineReducers;右側(cè)表示各實(shí)體 reducer 的返回值,最后匯總整合成 nextState

看了上圖,您應(yīng)該能直觀感受到為何取名為 reducer 了吧?把 state 分而治之,極大減輕開發(fā)與維護(hù)的難度

無(wú)論是 dispatch 哪個(gè) action,都會(huì)流通所有的 reducer
表面上看來(lái),這樣子很浪費(fèi)性能,但 JavaScript 對(duì)于這種純函數(shù)的調(diào)用是很高效率的,因此請(qǐng)盡管放心
這也是為何 reducer 必須返回其對(duì)應(yīng)的 state 的原因。否則整合狀態(tài)樹時(shí),該 reducer 對(duì)應(yīng)的鍵名就是 undefined

⊙ 源碼分析

僅截取關(guān)鍵部分,畢竟有很大一部分都是類型檢測(cè)警告

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]                       // 獲取當(dāng)前子 state
      var nextStateForKey = reducer(previousStateForKey, action) // 執(zhí)行各子 reducer 中獲取子 nextState
      nextState[key] = nextStateForKey                           // 將子 nextState 掛載到對(duì)應(yīng)的鍵名
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}

在此我的注釋很少,因?yàn)榇a寫得實(shí)在是太過(guò)明了了,注釋反而影響閱讀
作者 Dan 用了大量的 for 循環(huán),的確有點(diǎn)不夠優(yōu)雅

§ bindActionCreators(actionCreators, dispatch)

這個(gè) API 有點(diǎn)雞肋,它無(wú)非就是做了這件事情:dispatch(ActionCreator(XXX))

⊙ 源碼分析
/* 為 Action Creator 加裝上自動(dòng) 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") {
      // 逐個(gè)裝上自動(dòng) dispatch 技能
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}
⊙ 應(yīng)用場(chǎng)景

簡(jiǎn)明教程中的 code-5 如下:

<--! 本代碼塊記為 code-5 -->



我們看到,調(diào)用 addTodo 這個(gè) Action Creator 后得到一個(gè) action,之后又要手動(dòng) dispatch(action)
如果是只有一個(gè)兩個(gè) Action Creator 還是可以接受,但如果有很多個(gè)那就顯得有點(diǎn)重復(fù)了(其實(shí)我覺(jué)得不重復(fù)哈哈哈)
這個(gè)時(shí)候我們就可以利用 bindActionCreators 實(shí)現(xiàn)自動(dòng) dispatch




綜上,這個(gè) API 沒(méi)啥卵用,尤其是異步場(chǎng)景下,基本用不上

§ applyMiddleware(...middlewares)

Redux 中文文檔 高級(jí) · Middleware 有提到中間件的演化由來(lái)

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

⊙ Middleware

說(shuō)白了,Redux 引入中間件機(jī)制,其實(shí)就是為了在 dispatch 前后,統(tǒng)一“做愛(ài)做的事”。。。
諸如統(tǒng)一的日志記錄、引入 thunk 統(tǒng)一處理異步 Action Creator 等都屬于中間件
下面是一個(gè)簡(jiǎn)單的打印動(dòng)作前后 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) { // 記為【錨點(diǎn)-1】,中間件內(nèi)可用的 API
  return function (dispatch) {                 // 記為【錨點(diǎn)-2】,傳入原 dispatch 的引用
    return function (action) {
      console.log("state before dispatch", middlewareAPI.getState())
  
      var returnValue = dispatch(action) // 還記得嗎,dispatch 的返回值其實(shí)還是 action
  
      console.log("state after dispatch", middlewareAPI.getState())

      return returnValue // 繼續(xù)傳給下一個(gè)中間件作為參數(shù) action
    }
  }
}
⊙ Store Enhancer

說(shuō)白了,Store 增強(qiáng)器就是對(duì)生成的 store API 進(jìn)行改造,這是它與中間件最大的區(qū)別(中間件不修改 store 的 API)
而改造 store 的 API 就要從它的締造者 createStore 入手。例如,Redux 的 API applyMiddleware 就是一個(gè) Store 增強(qiáng)器:

import compose from "./compose" // 這貨的作用其實(shí)就是 compose(f, g, h)(action) => f(g(h(action)))

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

  /* 傳入 createStore */
  return function(createStore) {
  
    /* 返回一個(gè)函數(shù)簽名跟 createStore 一模一樣的函數(shù),亦即返回的是一個(gè)增強(qiáng)版的 createStore */
    return function(reducer, preloadedState, enhancer) {
    
      // 用原 createStore 先生成一個(gè) store,其包含 getState / dispatch / subscribe / replaceReducer 四個(gè) API
      var store = createStore(reducer, preloadedState, enhancer)
      
      var dispatch = store.dispatch // 指向原 dispatch
      var chain = [] // 存儲(chǔ)中間件的數(shù)組
  
      // 提供給中間件的 API(其實(shí)都是 store 的 API)
      var middlewareAPI = {
        getState: store.getState,
        dispatch: (action) => dispatch(action)
      }
      
      // 給中間件“裝上” API,見上面 ⊙Middleware【降低逼格寫法】的【錨點(diǎn)-1】 
      chain = middlewares.map(middleware => middleware(middlewareAPI))
      
      // 串聯(lián)各個(gè)中間件,為各個(gè)中間件傳入原 store.dispatch,見【降低逼格寫法】的【錨點(diǎn)-2】
      dispatch = compose(...chain)(store.dispatch)
  
      return {
        ...store, // store 的 API 中保留 getState / subsribe / replaceReducer
        dispatch  // 新 dispatch 覆蓋原 dispatch,往后調(diào)用 dispatch 就會(huì)觸發(fā) chain 內(nèi)的中間件鏈?zhǔn)酱?lián)執(zhí)行
      }
    }
  }
}

最終返回的雖然還是 store 的那四個(gè) API,但其中的 dispatch 函數(shù)的功能被增強(qiáng)了,這就是所謂的 Store Enhancer

⊙ 綜合應(yīng)用 ( 在線演示 )



  




控制臺(tái)輸出:

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

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

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

實(shí)際上,上面生成 store 的代碼可以更加優(yōu)雅:

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

如果有多個(gè)中間件以及多個(gè)增強(qiáng)器,還可以這樣寫(請(qǐng)留意序號(hào)順序):

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

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

const store = createStore(
  reducer,
  preloadedState, // <----- 可選,前后端同構(gòu)的數(shù)據(jù)同步
  compose( // <------------ 還記得嗎?compose 是從右到左的哦!
    applyMiddleware( // <-- 這貨也是 Store Enhancer 哦!但這是關(guān)乎中間件的增強(qiáng)器,必須置于 compose 執(zhí)行鏈的最后
      middleware1,
      middleware2,
      middleware3
    ),
    enhancer3,
    enhancer2,
    enhancer1
  )
)

為什么會(huì)支持那么多種寫法呢?在 createStore 的源碼分析的開頭部分,我省略了一些代碼,現(xiàn)在奉上該壓軸部分:

/** 本代碼塊記為 code-12 **/
if (typeof preloadedState === "function" && typeof enhancer === "undefined") {
  // 這里就是上面 code-10 的情況,只傳入 reducer 和 Store Enhancer 這兩個(gè)參數(shù)
  enhancer = preloadedState
  preloadedState = undefined
}

if (typeof enhancer !== "undefined") {
  if (typeof enhancer !== "function") {
    throw new Error("Expected the enhancer to be a function.")
  }
  // 存在 enhancer 就立即執(zhí)行,返回增強(qiáng)版的 createStore <--------- 記為【錨點(diǎn) 12-1】
  return enhancer(createStore)(reducer, preloadedState)
}

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

// 除 compose 外,createStore 竟然也在此為我們提供了書寫的便利與自由度,實(shí)在是太體貼了

如果像 code-11 那樣有多個(gè) enhancer,則 code-12 【錨點(diǎn) 12-1】 中的代碼會(huì)執(zhí)行多次
生成最終的超級(jí)增強(qiáng)版 store。最后,奉上 code-11compose 內(nèi)部的執(zhí)行順序示意圖:

原 createStore ————
                  │
                  ↓
return enhancer1(createStore)(reducer, preloadedState, enhancer2)
   |
   ├———————→ createStore 增強(qiáng)版 1
                    │
                    ↓
return enhancer2(createStore1)(reducer, preloadedState, enhancer3)
   |
   ├———————————→ createStore 增強(qiáng)版 1+2
                        │
                        ↓
return enhancer3(createStore1+2)(reducer, preloadedState, applyMiddleware(m1,m2,m3))
   |
   ├————————————————————→ createStore 增強(qiáng)版 1+2+3
                                     │
                                     ↓
return appleMiddleware(m1,m2,m3)(createStore1+2+3)(reducer, preloadedState)
   |
   ├——————————————————————————————————→ 生成最終增強(qiáng)版 store
§ 總結(jié)

Redux 有五個(gè) API,分別是:

createStore(reducer, [initialState])

combineReducers(reducers)

applyMiddleware(...middlewares)

bindActionCreators(actionCreators, dispatch)

compose(...functions)

createStore 生成的 store 有四個(gè) API,分別是:

getState()

dispatch(action)

subscribe(listener)

replaceReducer(nextReducer)

至此,若您已經(jīng)理解上述 API 的作用機(jī)理,以及中間件與增強(qiáng)器的概念/區(qū)別
本人將不勝榮幸,不妨點(diǎn)個(gè) star 算是對(duì)我的贊賞
如您對(duì)本教程有任何意見或改進(jìn)的建議,歡迎 issue,我會(huì)盡快予您答復(fù)

最后奉上 React + Redux + React Router 的簡(jiǎn)易留言板實(shí)例:react-demo

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

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/80156.html

相關(guān)文章

  • Redux 莞式教程 簡(jiǎn)明

    摘要:只要一個(gè)有,那無(wú)論用什么設(shè)備訪問(wèn),都會(huì)得到這個(gè)還原也是相當(dāng)簡(jiǎn)單把數(shù)據(jù)庫(kù)備份導(dǎo)入到另一臺(tái)機(jī)器,部署同樣的運(yùn)行環(huán)境與代碼。純粹只是一個(gè)狀態(tài)管理庫(kù),幾乎可以搭配任何框架使用上述例子連都沒(méi)用哦親下一章進(jìn)階教程 Redux 簡(jiǎn)明教程 原文鏈接(保持更新):https://github.com/kenberkele... 寫在前面 本教程深入淺出,配套 簡(jiǎn)明教程、進(jìn)階教程(源碼精讀)以及文檔注釋...

    notebin 評(píng)論0 收藏0
  • Redux 進(jìn)階 - react 全家桶學(xué)習(xí)筆記(二)

    摘要:在函數(shù)式編程中,異步操作修改全局變量等與函數(shù)外部環(huán)境發(fā)生的交互叫做副作用通常認(rèn)為這些操作是邪惡骯臟的,并且也是導(dǎo)致的源頭。 注:這篇是17年1月的文章,搬運(yùn)自本人 blog... https://github.com/BuptStEve/... 零、前言 在上一篇中介紹了 Redux 的各項(xiàng)基礎(chǔ) api。接著一步一步地介紹如何與 React 進(jìn)行結(jié)合,并從引入過(guò)程中遇到的各個(gè)痛點(diǎn)引出 ...

    Godtoy 評(píng)論0 收藏0
  • 前端進(jìn)階資源整理

    摘要:前端進(jìn)階進(jìn)階構(gòu)建項(xiàng)目一配置最佳實(shí)踐狀態(tài)管理之痛點(diǎn)分析與改良開發(fā)中所謂狀態(tài)淺析從時(shí)間旅行的烏托邦,看狀態(tài)管理的設(shè)計(jì)誤區(qū)使用更好地處理數(shù)據(jù)愛(ài)彼迎房源詳情頁(yè)中的性能優(yōu)化從零開始,在中構(gòu)建時(shí)間旅行式調(diào)試用輕松管理復(fù)雜狀態(tài)如何把業(yè)務(wù)邏輯這個(gè)故事講好和 前端進(jìn)階 webpack webpack進(jìn)階構(gòu)建項(xiàng)目(一) Webpack 4 配置最佳實(shí)踐 react Redux狀態(tài)管理之痛點(diǎn)、分析與...

    BlackMass 評(píng)論0 收藏0
  • 前端文檔收集

    摘要:系列種優(yōu)化頁(yè)面加載速度的方法隨筆分類中個(gè)最重要的技術(shù)點(diǎn)常用整理網(wǎng)頁(yè)性能管理詳解離線緩存簡(jiǎn)介系列編寫高性能有趣的原生數(shù)組函數(shù)數(shù)據(jù)訪問(wèn)性能優(yōu)化方案實(shí)現(xiàn)的大排序算法一怪對(duì)象常用方法函數(shù)收集數(shù)組的操作面向?qū)ο蠛驮屠^承中關(guān)鍵詞的優(yōu)雅解釋淺談系列 H5系列 10種優(yōu)化頁(yè)面加載速度的方法 隨筆分類 - HTML5 HTML5中40個(gè)最重要的技術(shù)點(diǎn) 常用meta整理 網(wǎng)頁(yè)性能管理詳解 HTML5 ...

    jsbintask 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<