Redux 莞式教程 之 进阶篇

Redux 进阶教程

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

写在前面

置信您已看过 Redux 简明教程,本教程是简明教程的实战化版本,陪伴源码剖析
Redux 用的是 ES6 编写,看到有迷惑的处所的,能够复制粘贴到这里在线编译 ES5

§ Redux API 总览

在 Redux 的源码目次 src/,我们能够看到以下文件构造:

├── utils/
│     ├── warning.js # 打酱油的,担任在控制台显现正告信息
├── applyMiddleware.js
├── bindActionCreators.js
├── combineReducers.js
├── compose.js
├── createStore.js
├── index.js # 进口文件

除去打酱油的 utils/warning.js 以及进口文件 index.js,剩下那 5 个就是 Redux 的 API

§ compose(…functions)

先说这个 API 的缘由是它没有依靠,是一个纯函数

⊙ 源码剖析

/**
 * 看起来逼格很高,现实运用现实上是这模样的:
 * 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 的实例(在线演示):

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

说白了:在异步 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

拓展浏览:中间件的洋葱模子

    原文作者:kenberkeley
    原文地址: https://segmentfault.com/a/1190000006701801
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞