浅谈对于react-thunk中间件的简单理解


Posted in Javascript onMay 01, 2019

前言

刚来公司的时候,对react项目中的thunk中间件的作用一直不太了解,最近有时间决定好好研究一下。鉴于本人初次写博客,并已假设读者已掌握redux的一些基本用法;如有错误,还望指出。不胜感激!

首先简单回顾一下redux工作流程

浅谈对于react-thunk中间件的简单理解

图画的不太好,见谅;

对于reactUI组件来说,数据的来源无外乎两种,一种是用户主动触发的动作,例如点击事件、提交表单,输入操作;另一种是组件主动的数据更新,如获取页面初始数据,子组件接受父组件的props变化而进行更新视图操作;

如图所示,无论那种对于数据的操作,对于view都会派发出一个action

状态的更新

正如我们所知,在redux里,每次更新后的Store都会对应着一个新的view,而Store里面数据的更新依赖action的触发————Store.dispatch(action)会自执行初始化中createStore中注入的reducers,从而计算出新的状态。

import { createStore } from 'redux'
//reducer 计算状态的纯函数
//initialState 初始化数据
//enhancers中间件
createStore(reducers, initialState, enhancers)

action的使用和插件的扩展

对于组件的输入操作(如点击事件),可以将store.dispatch(action)绑定到组件

const store = createStore(reducer);
 const TodoList = ({ state, someActionCreator }) => (
   <ul>
    {state.map(someState =>
      <Todo
        key={someState.someData}
        onClick={() => store.dispatch(someActionCreator(state.someData))}
     />
    </ul>
    )

或者通过connect方法,从组件的props中拿到dispatch方法,发出一个action

// 将state注入到组件的props里
 // 注意,这里的state指的是redux管理的数据,每一个view的状态对应着
 //  唯一的state;
 //  state的集合就是redux管理的store
const mapStateToProps = store => ({
     state: store.state
})
​
 // 将action注入到组件的props 里
const mapDispatchToProps = dispatch => ({
 actions: state => dispatch(actionCreators(state))
})
​
export default connect(
 mapStateToProps,
 mapDispatchToProps
)(TodoList)

然后组件绑定事件就可以改成这样 ,( actionCreators用于生成action, 参考官方链接 https://redux.js.org/basics/actions)

const TodoList = ({ state, actions }) => (
  `<ul>
    {state.map(someState =>
      <Todo
        key={someState.someData}
        onClick={() => actions(someState.someData)}
     />
    </ul>`
    )

那么问题来了,dispatch是同步执行reducers生成新状态的,对于页面的操作没有问题;但是如果点击事件是请求了某个结果,需要等待结果响应后再更新视图呢?应该如何处理?

因而redux引入了thunk中间件,对action进行了扩展

##thunk中间件解决了什么问题?

引入thunk插件后,我们可以在actionCreators内部编写逻辑,处理请求结果。而不只是单纯的返回一个action对象。

//未引入前的写法
let nextTodoId = 0
export const addTodo = text => ({
   type: 'ADD_TODO',
   id: nextTodoId++,
   text
 })
 
 //引入thunk后
 let nextTodoId = 0
 export const addTodo = text => ({
   return async dispatch => {
   //dosomething, request
   await request()
   dispatch({
   type: 'ADD_TODO',
   id: nextTodoId++,
   text
 })
   }
 })

thunk中间件的使用方法

import { applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';

const store = createStore(
 reducer,
 applyMiddleware(thunk)
);

createStore其实可以接受三个参数,第二个参数preloadedState一般作为整个应用的初始化数据,如果传入了这个参数,applyMiddleware就会被当做第三个参数处理

const store = createStore(
 reducer,
 initialState,
 applyMiddleware(thunk)
);

中间件都要放到applyMiddleware里,如果要添加中间件,可以依次添加,但是要遵循文档定义的顺序

const store = createStore(
 reducer,
 initialState,
 applyMiddleware(thunk,middleware1, middleware2)
);

源码解读

也许你会奇怪,为什么使用的时候要按照上面的写法,那我们就一起看下方法的实现

首先是createStore的参数顺序

function createStore(reducer, preloadedState, enhancer) {
 var _ref2;

 if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
  enhancer = preloadedState;
  preloadedState = undefined;
 }

 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.');
 }

第一个判断已经告诉了我们答案,参数的类型检验结果决定了顺序

applyMiddleware是干什么用的
function applyMiddleware() {
 for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {
  middlewares[_key] = arguments[_key];
 }

 return function (createStore) {
  return function () {
   for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
    args[_key2] = arguments[_key2];
   }

   var store = createStore.apply(undefined, args);
   var _dispatch = function dispatch() {
    throw new Error('Dispatching while constructing your middleware is not allowed. ' + 'Other middleware would not be applied to this dispatch.');
   };

   var middlewareAPI = {
    getState: store.getState,
    dispatch: function dispatch() {
     return _dispatch.apply(undefined, arguments);
    }
   };
   var chain = middlewares.map(function (middleware) {
    return middleware(middlewareAPI);
   });
   _dispatch = compose.apply(undefined, chain)(store.dispatch);

   return _extends({}, store, {
    dispatch: _dispatch
   });
  };
 };
}

代码不多,而且非常清晰:

1、applyMiddleware顾名思义,用于调用各种中间件;
2、applyMiddleware执行后,将所有入参中间件存入一个数组,并且返回一个闭包(闭包的概念不做累述)
3、闭包接受一个createStore作为入参并且执行后返回下一个闭包,createStore这个入参有没有很眼熟,没错,就是redux的createStore。

返回结果

返回将所有中间件串联存入的dispatch,执行时从右向左执行,第一次的执行结果会返回给一下个,依次类推。

如何实现每个中间件串联执行

_dispatch = compose.apply(undefined, chain),使用了一个compose函数,调用之后就可以将所有中间件串联起来,那么compose又是如何实现的呢?

精华所在

function compose() {
 for (var _len = arguments.length, funcs = Array(_len), _key = 0; _key < _len; _key++) {
  funcs[_key] = arguments[_key];
 }

 if (funcs.length === 0) {
  return function (arg) {
   return arg;
  };
 }

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

 return funcs.reduce(function (a, b) {
  return function () {
   return a(b.apply(undefined, arguments));
  };
 });
}

个人认为这个compose函数是整个redux中非常亮眼的部分,短短几行代码,就完成了一个核心功能的扩展,是责任链设计模式的经典体现。

ALSO 我们也可以使用这个compose方法对applyMiddleware进行扩展

let devtools = () => noop => {
     console.log(noop);
     return noop;  //createStore
   };
const enhancers = [
  applyMiddleware(...middleware),
  devtools()
 ];
createStore(reducers, initialState, compose(...enhancers));

然后回来,我们就明白了createStore中的设计

//如果存在中间件参数,那么将会得到一个经过改装的dispatch
// return _extends({}, store, {dispatch: _dispatch});
if (typeof enhancer !== 'undefined') {
  if (typeof enhancer !== 'function') {
   throw new Error('Expected the enhancer to be a function.');
  }

  return enhancer(createStore)(reducer, preloadedState);
 }

dispatch经过了怎样的改装

如上已经说过,compose会将传入的函数数组从右向左串联执行

compose.apply(undefined, chain)(store.dispatch);

thunk一定会接受上一个中间件的执行结果继续执行,然后最终在createState里返回一个改造好的dispatch, 接下来我只要看下thunk是怎样实现的,就了解了整个中间件使用的原理:

function createThunkMiddleware(extraArgument) {
 return function (_ref) {
  var dispatch = _ref.dispatch,
    getState = _ref.getState;
  return function (next) {
     //最终的dispatch
     //next就是接收的store.dispatch参数,为上一个中间件改造过的dispatch
   return function (action) {
    if (typeof action === 'function') {
     return action(dispatch, getState, extraArgument);
    }

    return next(action);
   };
  };
 };
}

var thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

代码同样精炼,改造后的dispatch入参接受的数据类型:

1、非function,不处理,将action 传给下一个中间件,最终都会根据传入的action计算相应的reducers(开头说的自执行)————store.dispatch(action)
2、function类型的action, 自动触发函数,并且将store.dispatch传入

总结

再结合开始介绍的thunk用法,我们就明白了thunk的原理,可以在actionCreators里通过返回一个函数,然后就可以在函数里编写某些异步操作了,待异步操作结束,最后通过传入的store.dispatch,发出action通知给Store要进行状态更新。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
基于jquery的弹出提示框始终处于窗口的居中位置(类似于alert弹出框的效果)
Sep 28 Javascript
html文件中jquery与velocity变量中的$冲突的解决方法
Nov 01 Javascript
jQuery实现动态添加和删除一个div
Aug 12 Javascript
很棒的js选项卡切换效果
Jul 15 Javascript
微信小程序 开发之滑块视图容器(swiper)详解及实例代码
Feb 22 Javascript
javascript实现最长公共子序列实例代码
Feb 05 Javascript
JS中promise化微信小程序api
Apr 12 Javascript
Vue使用vux-ui自定义表单验证遇到的问题及解决方法
May 10 Javascript
angular6的响应式表单的实现
Oct 10 Javascript
Node.js console控制台简单用法分析
Jan 04 Javascript
layUI实现前端分页和后端分页
Jul 27 Javascript
antd-DatePicker组件获取时间值,及相关设置方式
Oct 27 Javascript
vue增加强缓存和版本号的实现方法
May 01 #Javascript
vue组件化中slot的基本使用方法
May 01 #Javascript
Vue基本使用之对象提供的属性功能
Apr 30 #Javascript
原生JS实现随机点名项目的实例代码
Apr 30 #Javascript
vue实现随机验证码功能的实例代码
Apr 30 #Javascript
详解vue 图片上传功能
Apr 30 #Javascript
vue移动端屏幕适配详解
Apr 30 #Javascript
You might like
虫族 Zerg 魔法科技
2020/03/14 星际争霸
Php获取金书网的书名的实现代码
2010/06/11 PHP
PHP与jquery实时显示网站在线人数实例详解
2016/12/02 PHP
JQuery.ajax传递中文参数的解决方法 推荐
2011/03/28 Javascript
用Vue.js实现监听属性的变化
2016/11/17 Javascript
浅析JavaScript的几种Math函数,random(),ceil(),round(),floor()
2016/12/22 Javascript
详解Jquery 遍历数组之$().each方法与$.each()方法介绍
2017/01/09 Javascript
vue使用Axios做ajax请求详解
2017/06/07 Javascript
node文字生成图片的示例代码
2017/10/26 Javascript
vue.js根据代码运行环境选择baseurl的方法
2018/02/28 Javascript
JavaScript生成指定范围的时间列表
2018/03/19 Javascript
在create-react-app中使用css modules的示例代码
2018/07/31 Javascript
详解在React-Native中持久化redux数据
2019/05/22 Javascript
一文了解vue-router之hash模式和history模式
2019/05/31 Javascript
压缩Vue.js打包后的体积方法总结(Vue.js打包后体积过大问题)
2020/02/03 Javascript
Vue中keep-alive 实现后退不刷新并保持滚动位置
2020/03/17 Javascript
Vue循环中多个input绑定指定v-model实例
2020/08/31 Javascript
Nodejs在局域网配置https访问的实现方法
2020/10/17 NodeJs
vue3+typeScript穿梭框的实现示例
2020/12/29 Vue.js
Python提示[Errno 32]Broken pipe导致线程crash错误解决方法
2014/11/19 Python
python执行shell获取硬件参数写入mysql的方法
2014/12/29 Python
使用简单工厂模式来进行Python的设计模式编程
2016/03/01 Python
python3 tkinter实现点击一个按钮跳出另一个窗口的方法
2019/06/13 Python
python获取地震信息 微信实时推送
2019/06/18 Python
Python 从subprocess运行的子进程中实时获取输出的例子
2019/08/14 Python
python列表推导式入门学习解析
2019/12/02 Python
python——全排列数的生成方式
2020/02/26 Python
小学岗位竞聘方案
2014/01/22 职场文书
酒后驾驶检讨书
2014/01/27 职场文书
毕业生如何写自荐信
2014/03/26 职场文书
小学生竞选班干部演讲稿(5篇)
2014/09/12 职场文书
学生退学证明
2015/06/23 职场文书
2019辞职报告范本3篇!
2019/07/23 职场文书
Java中PriorityQueue实现最小堆和最大堆的用法
2021/06/27 Java/Android
VUE使用draggable实现组件拖拽
2022/04/06 Vue.js
MySQL数据库配置信息查看与修改方法详解
2022/06/25 MySQL