浅谈对于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 相关文章推荐
用javascript实现页面打印的三种方法
Mar 05 Javascript
javascript 控制弹出窗口
Apr 10 Javascript
细品javascript 寻址,闭包,对象模型和相关问题
Apr 27 Javascript
jQuery ajax 路由和过滤器使用说明
Aug 02 Javascript
微信小程序 wxapp内容组件 progress详细介绍
Oct 31 Javascript
探索Vue.js component内容实现
Nov 03 Javascript
Javascript中for循环语句的几种写法总结对比
Jan 23 Javascript
让微信小程序支持ES6中Promise特性的方法详解
Jun 13 Javascript
js实现图片放大展示效果
Aug 30 Javascript
解决三元运算符 报错“SyntaxError: can''t assign to conditional expression”
Feb 12 Javascript
vue+element-ui JYAdmin后台管理系统模板解析
Jul 28 Javascript
在webstorm中配置less的方法详解
Sep 25 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
PHP调用三种数据库的方法(1)
2006/10/09 PHP
PHP setcookie指定domain参数后,在IE下设置cookie失效的解决方法
2011/09/09 PHP
在wamp集成环境下升级php版本(实现方法)
2013/07/01 PHP
PHP获取ip对应地区和使用网络类型的方法
2015/03/11 PHP
PHP sleep()函数, usleep()函数
2016/08/25 PHP
PHP面向对象程序设计之接口的继承定义与用法详解
2018/12/20 PHP
IE中jscript/javascript的条件编译
2006/09/07 Javascript
html 锁定页面(js遮罩层弹出div效果)
2009/10/27 Javascript
jQuery ReferenceError: $ is not defined 错误的处理办法
2013/05/10 Javascript
jQuery列表拖动排列具体实现
2013/11/04 Javascript
js hover 定时器(实例代码)
2013/11/12 Javascript
javascript中的undefined和not defined区别示例介绍
2014/02/26 Javascript
JavaScript通过join函数连接数组里所有元素的方法
2015/03/20 Javascript
JavaScript 里的类数组对象
2015/04/08 Javascript
JavaScript File API文件上传预览
2016/02/02 Javascript
js实现的光标位置工具函数示例
2016/10/03 Javascript
Javascript 正则表达式校验数字的简单实例
2016/11/02 Javascript
Bootstrap Search Suggest使用例子
2016/12/21 Javascript
详解JavaScript基础知识(JSON、Function对象、原型、引用类型)
2018/01/16 Javascript
JavaScript运行原理分析
2018/02/09 Javascript
移动端底部导航固定配合vue-router实现组件切换功能
2019/06/13 Javascript
nodejs语言实现验证码生成功能的示例代码
2019/10/13 NodeJs
使用webpack/gulp构建TypeScript项目的方法示例
2019/12/18 Javascript
Vue.js的模板语法详解
2020/02/16 Javascript
[03:59]第二届DOTA2亚洲邀请赛选手传记-VGJ.rOtk
2017/04/03 DOTA
python操作mysql数据库
2017/03/05 Python
Python数据结构之栈、队列的实现代码分享
2017/12/04 Python
一些Centos Python 生产环境的部署命令(推荐)
2018/05/07 Python
在脚本中单独使用django的ORM模型详解
2020/04/01 Python
Python如何爬取b站热门视频并导入Excel
2020/08/10 Python
纽约通行卡:The New York Pass(免费游览纽约90多个景点)
2017/07/29 全球购物
Timberland俄罗斯官方网上商店:全球领先的户外品牌
2020/03/15 全球购物
元旦寄语大全
2014/04/10 职场文书
推荐信模板
2014/05/09 职场文书
公司备用金管理制度
2015/08/04 职场文书
python manim实现排序算法动画示例
2022/08/14 Python