浅谈对于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 相关文章推荐
Prototype使用指南之string.js
Jan 10 Javascript
JavaScript 联动的无限级封装类,数据采用非Ajax方式,随意添加联动
Jun 29 Javascript
Dreamweaver jQuery智能提示插件,支持版本提示,支持1.6api
Jul 31 Javascript
基于JS2Image实现圣诞树代码
Dec 24 Javascript
jQuery实现可以控制图片旋转角度效果(附demo源码下载)
Jan 27 Javascript
Bootstrap fileinput组件封装及使用详解
Mar 10 Javascript
Angular2.js实现表单验证详解
Jun 23 Javascript
解决vue的 v-for 循环中图片加载路径问题
Sep 03 Javascript
使用JavaScript解析URL的方法示例
Mar 01 Javascript
微信小程序实现元素渐入渐出动画效果封装方法
May 18 Javascript
json数据格式常见操作示例
Jun 13 Javascript
Javascript生成器(Generator)的介绍与使用
Jan 31 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函数serialize()与unserialize()用法实例
2014/11/06 PHP
php计算2个日期的差值函数分享
2015/02/02 PHP
网页javascript精华代码集
2007/01/24 Javascript
JS中引用百度地图并将百度地图的logo和信息去掉
2013/09/29 Javascript
用jquery修复在iframe下的页面锚点失效问题
2014/08/22 Javascript
Javscript调用iframe框架页面中函数的方法
2014/11/01 Javascript
JS实现固定在右下角可展开收缩DIV层的方法
2015/02/13 Javascript
不间断循环滚动效果的实例代码(必看篇)
2016/10/08 Javascript
vue-cli入门之项目结构分析
2017/04/20 Javascript
js学习总结_选项卡封装(实例讲解)
2017/07/13 Javascript
基于javaScript的this指向总结
2017/07/22 Javascript
vue jsx 使用指南及vue.js 使用jsx语法的方法
2017/11/11 Javascript
微信小程序页面跳转功能之从列表的item项跳转到下一个页面的方法
2017/11/27 Javascript
JS计算输出100元钱买100只鸡问题的解决方法
2018/01/04 Javascript
Vue实现搜索 和新闻列表功能简单范例
2018/03/16 Javascript
详解Puppeteer 入门教程
2018/05/09 Javascript
浅谈Javascript中的对象和继承
2019/04/19 Javascript
JavaScript 浏览器对象模型BOM原理与常见用法实例分析
2019/12/16 Javascript
Vue移动端实现图片上传及超过1M压缩上传
2019/12/23 Javascript
Python使用turtule画五角星的方法
2015/07/09 Python
Python创建或生成列表的操作方法
2019/06/19 Python
django搭建项目配置环境和创建表过程详解
2019/07/22 Python
Python实现寻找回文数字过程解析
2020/06/09 Python
Python 实现自动登录+点击+滑动验证功能
2020/06/10 Python
django使用channels实现通信的示例
2020/10/19 Python
CSS3轻松实现清新 Loading 效果的简单实例
2016/06/06 HTML / CSS
澳大利亚票务和娱乐市场领导者:Ticketmaster
2017/03/03 全球购物
消防战士优秀事迹材料
2014/02/13 职场文书
参观接待方案
2014/03/17 职场文书
爱心倡议书范文
2014/05/12 职场文书
装饰工程师岗位职责
2014/06/08 职场文书
实习护士自荐信
2015/03/25 职场文书
详解TS数字分隔符和更严格的类属性检查
2021/05/06 Javascript
MySQL快速插入一亿测试数据
2021/06/23 MySQL
Java中API的使用方法详情
2022/04/06 Java/Android
kubernetes集群搭建Zabbix监控平台的详细过程
2022/07/07 Servers