摘要

在 React 的世界中,状态治理计划不下几百种,但其中最经典的,莫过于 Redux 。如果你想学习函数式编程,那么 Redux
源码就是最好的学习材料。思考到不少小伙伴用的是 Vue ,自己争取让这篇文章写得通俗易懂,让没有接触过 React 技术栈的同学也能把握
Redux 。

在 React 的世界中,状态治理计划不下几百种,但其中最经典的,莫过于 Redux 。如果你想学习函数式编程,那么 Redux 源码就是最好的学习材料。思考到不少小伙伴用的是 Vue ,自己争取让这篇文章写得通俗易懂,让没有接触过 React 技术栈的同学也能把握 Redux 。

在 React 的世界中,状态治理计划不下几百种,但其中最经典的,莫过于 Redux 。如果你想学习函数式编程,那么 Redux 源码就是最好的学习材料。思考到不少小伙伴用的是 Vue ,自己争取让这篇文章写得通俗易懂,让没有接触过 React 技术栈的同学也能把握 Redux 。

Redux 属于典型的“百行代码,千行文档”,其中外围代码非常少,然而思维不简略,能够总结为上面两点:

  • 全局状态惟一且不可变(Immutable) ,不可变的意思是当须要批改状态的时候,用一个新的来替换,而不是间接在原数据上做更改:

    let store = { foo: 1, bar: 2 };// 当须要更新某个状态的时候// 创立一个新的对象,而后把原来的替换掉store = { ...store, foo: 111 };

这点与 Vue 恰好相反,在 Vue 中必须间接在原对象上批改,能力被响应式机制监听到,从而触发 setter 告诉依赖更新。

状态更新通过一个纯函数(Reducer)实现。纯函数(Pure function)的特点是:

  • 输入仅与输出无关;
  • 援用通明,不依赖内部变量;
  • 不产生副作用;

因而对于一个纯函数,雷同的输出肯定会产生雷同的输入,十分稳固。应用纯函数进行全局状态的批改,使得全局状态能够被预测。

1. 须要理解的几个概念

在应用 Redux 及浏览源码之前须要理解上面几个概念:

Action

action 是一个一般 JavaScript 对象,用来形容如何批改状态,其中须要蕴含 type 属性。一个典型的 action 如下所示:

const addTodoAction = {  type: 'todos/todoAdded',  payload: 'Buy milk'}

Reducers

reducer 是一个纯函数,其函数签名如下:

/** * @param {State} state 以后状态 * @param {Action} action 形容如何更新状态 * @returns 更新后的状态 */function reducer(state: State, action: Action): State

reducer 函数的名字来源于数组的 reduce 办法,因为它们相似数组 reduce 办法传递的回调函数,也就是上一个返回的值会作为下一次调用的参数传入。

reducer函数的编写须要严格遵顼以下规定:

  • 查看reducer是否关怀以后的action

    • 如果是,就创立一份状态的正本,应用新的值更新正本中的状态,而后返回这个正本
  • 否则就返回以后状态

一个典型的 reducer 函数如下:

const initialState = { value: 0 }function counterReducer(state = initialState, action) {  if (action.type === 'counter/incremented') {    return {      ...state,      value: state.value + 1    }  }  return state}

Store

通过调用 createStore 创立的 Redux 利用实例,能够通过 getState() 办法获取到以后状态。

Dispatch

store 实例裸露的办法。更新状态的惟一办法就是通过 dispatch 提交 action 。store 将会调用 reducer 执行状态更新,而后能够通过 getState() 办法获取更新后的状态:

store.dispatch({ type: 'counter/incremented' })console.log(store.getState())// {value: 1}

storeEnhancer

createStore 的高阶函数封装,用于加强 store 的能力。Redux 的 applyMiddleware 是官网提供的一个 enhancer 。

middleware

dispatch 的高阶函数封装,由 applyMiddleware 把原 dispatch替换为蕴含 middleware 链式调用的实现。Redux-thunk 是官网提供的 middleware,用于反对异步 action 。

2. 根本应用

学习源码之前,咱们先来看下 Redux 的根本应用,便于更好地了解源码。

首先咱们编写一个 Reducer 函数如下:

// reducer.jsconst initState = {  userInfo: null,  isLoading: false};export default function reducer(state = initState, action) {  switch (action.type) {    case 'FETCH_USER_SUCCEEDED':      return {        ...state,        userInfo: action.payload,        isLoading: false      };    case 'FETCH_USER_INFO':      return { ...state, isLoading: true };    default:      return state;  }}

在下面代码中:

  • reducer首次调用的时候会传入initState作为初始状态,而后switch...case最初的default用来获取初始状态
  • 在switch...case中还定义了两个action.type用来指定如何更新状态

接下来咱们创立 store :

// index.jsimport { createStore } from "redux";import reducer from "./reducer";const store = createStore(reducer);

store 实例会裸露两个办法 getState 和 dispatch ,其中 getState 用于获取状态,dispatch 用于提交 action 批改状态,同时还有一个 subscribe 用于订阅store的变动:

// index.js// 每次更新状态后订阅 store 变动store.subscribe(() => console.log(store.getState()));// 获取初始状态store.getState();// 提交 action 更新状态store.dispatch({ type: "FETCH_USER_INFO" });store.dispatch({ type: "FETCH_USER_SUCCEEDED", payload: "测试内容" });

咱们运行一下下面的代码,控制台会先后打印:

{ userInfo: null, isLoading: false } // 初始状态{ userInfo: null, isLoading: true } // 第一次更新{ userInfo: "测试内容", isLoading: false } // 第二次更新

3. Redux Core 源码剖析

下面的例子尽管很简略,然而曾经蕴含 Redux 的外围性能了。接下来咱们来看下源码是如何实现的。

createStore

能够说 Redux 设计的所有核心思想都在 createStore 外面了。 createStore 的实现其实非常简单,整体就是一个闭包环境,外面缓存了 currentReducer 和 currentState ,并且定义了getState、subscribe、dispatch 等办法。

createStore 的外围源码如下,因为这边还没用到 storeEnhancer ,结尾有些if...else的逻辑被省略了,顺便把源码中的类型注解也都去掉了,不便浏览:

// src/createStore.tsfunction createStore(reducer, preloadState = undefined) {  let currentReducer = reducer;  let currentState = preloadState;  let listeners = [];  const getState = () => {    return currentState;  }  const subscribe = (listener) => {    listeners.push(listener);  }  const dispatch = (action) => {    currentState = currentReducer(currentState, action);    for (let i = 0; i < listeners.length; i++) {      const listener = listeners[i];      listener();    }    return action;  }    dispatch({ type: "INIT" });  return {    getState,    subscribe,    dispatch  }}

createStore 的调用链路如下:

  • 首先调用 createStore 办法,传入 reducer 和 preloadState 。preloadState 代表初始状态,如果不传那么 reducer 必须要指定初始值;
  • 将 reducer 和 preloadState 别离赋值给 currentReducer 和 currentState 用于创立闭包;
  • 创立 listeners 数组,这其实就是基于公布订阅模式,listeners 就是公布订阅模式的事件核心,也是通过闭包缓存;
  • 创立 getState 、subscribe 、dispatch 等函数;
  • 调用 dispatch 函数,提交一个 INIT 的 action 用来生成初始state,在 Redux 源码中,这里的 type 是一个随机数;
  • 最初返回一个蕴含 getState 、subscribe 、dispatch 函数的对象,即 store 实例;

那么很显然,外界无法访问到闭包的值,只能通过getState函数拜访。

为了订阅状态更新,能够应用 subscribe 函数向事件核心 push 监听函数(留神 listener 是容许副作用存在的)。

当须要更新状态时,调用 dispatch 提交 action 。在 dispatch 函数中调用 currentReducer(也就是 reducer 函数),并传入 currentState 和 action ,而后生成一个新的状态,传给 currentState 。在状态更新实现后,将订阅的监听函数执行一遍(实际上只有调用 dispatch ,即便没有对 state 做任何批改,也会触发监听函数)。

如果有相熟面向对象编程的小伙伴可能会说,createStore外面做的事件能够封装到一个类外面。的确能够,自己用 TypeScript 实现如下(公布订阅的性能不写了):

type State = Object;type Action = {  type: string;  payload?: Object;}type Reducer = (state: State, action: Action) => State;// 定义 IRedux 接口interface IRedux {  getState(): State;  dispatch(action: Action): Action;}// 实现 IRedux 接口class Redux implements IRedux {  // 成员变量设为公有  // 相当于闭包作用  private currentReducer: Reducer;  private currentState?: State;  constructor(reducer: Reducer, preloadState?: State) {    this.currentReducer = reducer;    this.currentState = preloadState;    this.dispatch({ type: "INIT" });  }    public getState(): State {    return this.currentState;  }  public dispatch(action: Action): Action {    this.currentState = this.currentReducer(      this.currentState,      action    );    return action;  }}// 通过工厂模式创立实例function createStore(reducer: Reducer, preloadState?: State) {  return new Redux(reducer, preloadState);}

你看,多有意思,函数式编程和面向对象编程居然必由之路了。

applyMiddleware

applyMiddleware 是 Redux 中的一个难点,尽管代码不多,然而外面用到了大量函数式编程技巧,自己也是通过大量源码调试才彻底搞懂。

首先要能看懂这种写法:

const middleware =  (store) =>    (next) =>      (action) => {        // ...      }

下面的写法相当于:

const middleware = function(store) {  return function(next) {    return function(action) {      // ...    }  }}

其次须要晓得,这种其实就是函数柯里化,也就是能够分步承受参数。如果内层函数存在变量援用,那么每次调用都会生成闭包。

说到闭包,有些同学马上就想到内存透露。但实际上闭包在平时我的项目开发中十分常见,很多时候咱们不经意间就创立了闭包,但往往都被咱们疏忽了。

闭包一大作用就是缓存值,这和申明一个变量在赋值的成果是相似的。而闭包的难点就在于,变量是显式申明,而闭包往往是隐式的,什么时候创立闭包,什么时候更新了闭包的值,很容易被疏忽。

能够这么说,函数式编程就是围绕闭包开展的。在上面的源码剖析中,会看到大量闭包的例子。

applyMiddleware 是 Redux 官网实现的 storeEnhancer ,实现了一套插件机制,减少 store 的能力,例如实现异步 Action ,实现 logger 日志打印,实现状态长久化等等。

export default function applyMiddleware<Ext, S = any>(  ...middlewares: Middleware<any, S, any>[]): StoreEnhancer<{ dispatch: Ext }>

个人观点,这样做的益处就是提供了造轮子的空间

applyMiddleware 承受一个或多个 middleware 实例,而后再传给createStore:

import { applyMiddleware, createStore } from "redux";import thunk from "redux-thunk"; // 应用 thunk 中间件import reducer from "./reducer";const store = createStore(reducer, applyMiddleware(thunk));

createStore 入参中只承受一个 storeEnhancer ,如果须要传入多个,能够应用 Redux Utils 中的 compose 函数将它们组合起来。

compose 函数在前面会介绍

看下面的用法,能够猜想 applyMiddleware 必定也是个高阶函数。之前说到 createStore 后面有些if..else逻辑因为没用到 storeEnhancer 所以被省略了。这边咱们一起来看下。

首先看 createStore 的函数签名,实际上是能够承受 1-3 个参数。其中 reducer 是必须要传递的。当第二个参数为函数类型,会辨认为 storeEnhancer。如果第二个参数不是函数类型,则会辨认为 preloadedState ,此时还能够再传递一个函数类型的 storeEnhancer :

function createStore(reducer: Reducer, preloadedState?: PreloadedState | StoreEnhancer, enhancer?: StoreEnhancer): Store

能够看到源码中参数校验的逻辑:

// src/createStore.ts:71if (  (typeof preloadedState === 'function' && typeof enhancer === 'function') ||  (typeof enhancer === 'function' && typeof arguments[3] === 'function')) {  // 传递两个函数类型参数的时候,抛出异样  // 也就是只承受一个 storeEnhancer  throw new Error();}

当第二个参数为函数类型,将它作为 storeEhancer 解决:

// src/createStore.ts:82if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {  enhancer = preloadedState as StoreEnhancer<Ext, StateExt>  preloadedState = undefined}

接下来是一个比拟难的逻辑:

// src/createStore.ts:87if (typeof enhancer !== 'undefined') {  // 如果应用了 enhancer  if (typeof enhancer !== 'function') {    // 如果 enhancer 不是函数就抛出异样    throw new Error();  }  // 间接返回调用 enhancer 之后的后果,并没有往下持续创立 store  // enhancer 必定是一个高阶函数  // 先传入了 createStore,又传入 reducer 和 preloadedState  // 阐明很有可能在 enhancer 外部再次调用 createStore  return enhancer(createStore)(    reducer,    preloadedState  )}

上面咱们来看一下 applyMiddleware 的源码,为便于浏览,把源码中的类型注解都去掉了:

// src/applyMiddleware.tsimport compose from './compose';function applyMiddleware(...middlewares) {  return (createStore) => (reducer, preloadedState) => {    const store = createStore(reducer, preloadedState);    let dispatch = () => {      throw new Error();    }    const middlewareAPI = {      getState: store.getState,      dispatch: (action, ...args) => dispatch(action, ...args)    }    const chain = middlewares.map(middleware => middleware(middlewareAPI));    dispatch = compose(...chain)(store.dispatch);    return {      ...store,      dispatch    }  }}

能够看到这里代码并不多,然而呈现了一个函数嵌套函数的情景:

const applyMiddleware = (...middlewares) =>  (createStore) =>    (reducer, preloadedState) => {      // ...    }

剖析一下源码中的调用链路:

  • 调用 applyMiddleware 时,传入中间件实例,返回 enhancer 。从残余参数的用法看出,反对传入多个 middleware ;
  • 由createStore调用 enhancer ,分两次传入 createStore 和 reducer 、preloadedState ;
  • 外部再次调用 createStore ,这次因为没有传 enhancer ,所以间接走创立 store 的流程;
  • 创立一个通过润饰的 dispatch 办法,笼罩默认 dispatch ;
  • 结构 middlewareAPI ,对 middleware 注入 middlewareAPI ;
  • 将 middleware 实例组合为一个函数,再向 middleware 传递默认的 store.dispatch 办法;
  • 最初返回一个新的 store 实例,此时 store 的 dispatch 办法通过了 middleware 润饰;

这里波及到 compose 函数,是函数式编程范式中常常用到的一种解决,创立一个从右到左的数据流,左边函数执行的后果作为参数传入右边,最终返回一个以上述数据流执行的函数:

// src/compose.ts:46export 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))  )}
思考题:如果心愿把执行程序改为从左往右,须要怎么改?

通过这边的代码,咱们不难推断出一个中间件的构造:

function middleware({ dispatch, getState }) {  // 接管 middlewareAPI  return function(next) {    // 接管默认的 store.dispatch 办法    return function(action) {      // 接管组件调用 dispatch 传入的 action      console.info('dispatching', action);      let result = next(action);      console.log('next state', store.getState());      return result;    }  }}

看到这里,我想大多数读者都会有两个问题:

  1. 通过 middlewareAPI 获取的 dispatch 函数和 store 实例最终裸露的 dispatch 函数都是通过润饰的吗;
  2. 为了避免在创立 middleware 的时候调用 dispatch ,applyMiddleware 给新的 dispatch 初始化为一个空函数,且调用会抛出异样,那么这个函数到底在何时被替换掉的;

大家能够先试着思考一下。

说实话,自己在浏览源码的时候也被这两个问题困扰,大多数技术文章也都没有给出解释。没方法,只能通过调试源码来找答案。通过一直调试,终于搞清楚了,middlewareAPI 的 dispatch 函数自身其实就是以闭包模式引入的,这个闭包可能没多少人能看得出来:

// 定义新的 dispatch 办法// 此时是一个空函数,调用会抛出异样let dispatch = () => {  throw new Error();}// 定义 middlewareAPI// 留神这里的 dispatch 是通过闭包模式引入的const middlewareAPI = {  getState: store.getState,  dispatch: (action, ...args) => dispatch(action, ...args)}// 对 middleware 注入 middlewareAPI// 此时在 middleware 中调用 dispatch 会抛出异样const chain = middlewares.map(middleware => middleware(middlewareAPI));

而后上面这段代码其实做了两件事,一方面将 middleware 组合为一个函数,注入默认 dispatch 函数,另一方面将新的 dispatch 初始的空函数替换为失常可执行的函数。同时因为 middlewareAPI 的 dispatch 是以闭包模式引入的,当 dispatch 更新之后,闭包中的值也相应更新:

// 将 dispatch 替换为失常的 dispatch 办法// 留神闭包中的值也会相应更新,middleware 能够拜访到更新后的办法dispatch = compose(...chain)(store.dispatch);

也就是说,createStore 生成的实例裸露的 dispatch 和 middleware 获取的都是润饰后的 dispatch ,并且应该是长这样:

function(action) {  // 留神这里存在闭包  // 能够获取到中间件初始化传入的 dispatch、getState 和 next  // 如果你打断点,能够在 scope 中看到闭包的变量  // 同时留神这里的 dispatch 就是这个函数自身  console.info('dispatching', action);  let result = next(action);  console.log('next state', store.getState());  return result;}

4. 解决异步 Action

因为 reducer 须要严格控制为纯函数,因而不能在外面进行异步操作,也不能进行网络申请。有些同学可能会说,尽管 reducer 外面不能放异步代码,然而能够把 dispatch 函数放在异步回调中调用呀:

setTimeout(() => {  this.props.dispatch({ type: 'HIDE_NOTIFICATION' })}, 5000)

在 React 组件中通常用 connect 把 dispatch 映射到组件的props 中,相似 Vuex 中的 mapAction 用法。

的确能够!Redux 作者 Dan Abramov 在 Stackoverflow 下面有一个十分好的答复,其中就同意了这种用法:

https://stackoverflow.com/questions/35411423/how-to-dispatch-a-redux-action-with-a-timeout/35415559#35415559

自己将 Dan Abramov 的外围观点总结如下。

  • Redux 的确提供了一些解决异步 Action 的代替办法,但应该只在当你意识到你编写了大量模板代码的时候再去应用。否则就用最简略的计划(如无必要,勿增实体);
  • 当多个组件须要用到同一个 action.type 时,为防止 action.type 拼写错误,须要抽离公共的 actionCreator,例如:
  // actionCreator.js  export function showNotification(text) {    return { type: 'SHOW_NOTIFICATION', text }  }  export function hideNotification() {    return { type: 'HIDE_NOTIFICATION' }  }  // component.js  import { showNotification, hideNotification } from '../actionCreator'  this.props.dispatch(showNotification('You just logged in.'))  setTimeout(() => {    this.props.dispatch(hideNotification())  }, 5000)
  • 下面的逻辑在简略场景下齐全可行,然而随着业务复杂度减少会呈现几个问题:

    1. 通常状态更新有好几个步骤,而且存在逻辑上的先后顺序,例如告诉的展现和暗藏,导致模板代码较多;
    2. 提交的 action 没有状态,如呈现竞争条件可能导致状态更新出 bug ;
  • 出于下面的问题,须要抽离异步的 actionCreator ,把波及状态更新的操作封装进去,便于复用,同时为每次 dispatch 生成惟一 id :
  // actions.js  function showNotification(id, text) {    return { type: 'SHOW_NOTIFICATION', id, text }  }  function hideNotification(id) {    return { type: 'HIDE_NOTIFICATION', id }  }  let nextNotificationId = 0  export function showNotificationWithTimeout(dispatch, text) {    const id = nextNotificationId++    dispatch(showNotification(id, text))    setTimeout(() => {      dispatch(hideNotification(id))    }, 5000)  }

而后在页面组件中这样应用,解决了模板代码和状态更新抵触问题:

  // component.js  showNotificationWithTimeout(this.props.dispatch, 'You just logged in.')  // otherComponent.js  showNotificationWithTimeout(this.props.dispatch, 'You just logged out.')
  • 仔细的同学应该留神到,这边传递了 dispatch 。这是因为,失常来说只有组件中能拜访到 dispatch ,为了能让内部封装的函数也能拜访,咱们须要将 dispatch 作为参数传过来;
  • 这时有些同学会提出质疑,如果将 store 作为全局单例,不就能够间接拜访了:
  // store.js  export default createStore(reducer)  // actions.js  import store from './store'  // ...  let nextNotificationId = 0  export function showNotificationWithTimeout(text) {    const id = nextNotificationId++    store.dispatch(showNotification(id, text))    setTimeout(() => {      store.dispatch(hideNotification(id))    }, 5000)  }  // component.js  showNotificationWithTimeout('You just logged in.')  // otherComponent.js  showNotificationWithTimeout('You just logged out.')
  • 下面这样从操作上来说的确可行,然而 Redux 团队并不同意单例的写法。他们的理由是,如果 store 变为单例,会导致服务端渲染的实现变得艰难,同时测试也不不便,如要改用 mock store 须要批改所有 import ;
  • 基于下面的起因,Redux 团队还是倡议应用函数参数将 dispatch 传递进来,只管这样很麻烦。那么有没有一种解决方案呢?有的,应用 Redux-thunk 就解决了这个问题;
  • 实际上,Redux-thunk 的作用是教 Redux 辨认函数类型的非凡 Action ;
  • 中间件启用后,当 dispatch 的 Action 为函数类型,Redux-thunk 就会给这个函数传入 dispatch 作为参数,须要留神最终 reducer 拿到的依然是一般 JavaScript 对象作为 Action :
  // actions.js  function showNotification(id, text) {    return { type: 'SHOW_NOTIFICATION', id, text }  }  function hideNotification(id) {    return { type: 'HIDE_NOTIFICATION', id }  }  let nextNotificationId = 0  export function showNotificationWithTimeout(text) {    return function (dispatch) {      const id = nextNotificationId++      dispatch(showNotification(id, text))      setTimeout(() => {        dispatch(hideNotification(id))      }, 5000)    }  }

在组件中应用如下:

  // component.js  this.props.dispatch(showNotificationWithTimeout('You just logged in.'))

好了,Dan Abramov 的观点就总结到这里。

看到这里大家应该分明 Redux-thunk 的作用了,Redux-thunk 自身并没有提供异步解决方案,实现异步就是应用最简略的办法,把 dispatch 函数放在异步回调中。很多时候咱们会封装异步的 actionCreator ,在异步操作中每次都须要把 dispatch 传递进去很麻烦,Redux-thunk 对 dispatch 函数进行高阶封装,容许承受函数类型的 Action ,同时给这个 Action 传入 dispatch 和 getState 作为参数,这样就不必每次手动传递。

在看源码之前,大家能够联合 applyMiddleware 源码,想一下 Redux-thunk 外部实现。

其实 Redux-thunk 实现原理非常简单,代码如下:

// src/index.ts:15function createThunkMiddleware(extraArgument) {  return ({ dispatch, getState }) =>    next =>      action => {        if (typeof action === 'function') {          return action(dispatch, getState, extraArgument)        }        return next(action)      }}

在 Redux-thunk 外部,首先会调用 createThunkMiddleware 办法失去一个高阶函数而后向外导出。这个函数就是咱们之前剖析的中间件构造:

({ dispatch, getState }) =>  next =>    action => {      if (typeof action === 'function') {        return action(dispatch, getState, extraArgument)      }      return next(action)    }

首先在初始化阶段,applyMiddleware 会为 thunk 先后注入 middlewareAPI (对应 dispatch 和 getState 形参)和 store.dispatch (即本来的 dispatch 办法,对应 next 形参)。

在初始化实现之后,store 实例的 dispatch 会被替换为一个通过润饰的 dispatch 办法(middlewareAPI 中的 dispatch 因为是闭包援用,也会被替换),用 dispatch.toString() 打印能够输入如下内容:

// 留神这里能够拜访到闭包中的 dispatch、getState 和 next// 初始化实现后的 dispatch 实际上就是上面这个函数自身action => {  if (typeof action === 'function') {    return action(dispatch, getState, extraArgument)  }  return next(action)}

接下来的事件就很简略了,当咱们提交一个函数类型的 Action :

// actions.jsconst setUserInfo = data => ({  type: "SET_USER_INFO",  payload: data})export const getUserInfoAction = userId => {  return dispatch => {    getUserInfo(userId)      .then(res => {        dispatch(setUserInfo(res));      })  }}// component.jsimport { getUserInfoAction } from "./actionCreator";this.props.dispatch(getUserInfoAction("666"));

当提交的 action 为函数类型的时候,就调用这个函数,而后传入 dispatch 、getState 、extraArgument 参数:

if (typeof action === 'function') {  return action(dispatch, getState, extraArgument)}

(从这里能够看出,除了 dispatch 之外,在函数类型的 Action 外部还能够拜访 getState 和 extraArgument)

当异步操作实现,调用 Redux-thunk 传递的 dispatch 办法提交对象类型 Action 时,还是进入这个被润饰的 dispatch 办法,只不过在判断类型的时候,进入了另一个分支:

return next(action);

这里的 next 就是 Redux 本来的 dispatch 办法,会将对象类型的 Action 提交给 reducer 办法,最终执行状态更新。

5. 总结

Redux 是一种十分经典的状态治理解决方案。它遵循函数式编程的准则,状态只读且不可变,只有通过纯函数能力更新状态。

然而 Redux 同样也存在着不少问题。首先,对于老手来说,上手老本较高,应用之前须要先理解函数式编程的概念和设计思维。其次,Redux 在理论开发中十分繁琐,即便实现一个很简略的性能,可能也须要同时批改 4-5 个文件,升高了开发效率。作为比照,Vuex 的上手老本非常低,对于老手十分敌对,应用也非常简单,既不须要异步中间件,也不须要额定的 UI binding ,在 Redux 中通过插件提供的性能,全副内置开箱即用。

对此,Redux 官网提供了一个封装计划 Redux Toolkit,社区也提供了很多封装计划,例如 Dva 、Rematch 等等,旨在简化 Redux 的应用,API 的封装上很多中央就是参考了 Vuex 。甚至还呈现了酷似 Vue 响应式、应用可变数据(Mutable)的 Mobx 状态治理计划。此外,React 官网团队也在近期推出了 Recoil 状态治理库。

参考

https://redux.js.org

https://github.com/reduxjs/redux

https://github.com/reduxjs/redux-thunk

https://stackoverflow.com/questions/35411423/how-to-dispatch-a-redux-action-with-a-timeout/35415559#35415559

coding优雅指南:函数式编程