浅谈对于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中的函数
Jan 22 Javascript
SOSO地图API使用(一)在地图上画圆实现思路与代码
Jan 15 Javascript
javascript两种function的定义介绍及区别说明
May 02 Javascript
jquery二级导航内容均分的原理及实现
Aug 13 Javascript
jQuery中data()方法用法实例
Dec 27 Javascript
jquery+javascript编写国籍控件
Feb 12 Javascript
浅谈JavaScript异常处理语句
Jun 26 Javascript
jQuery的事件委托实例分析
Jul 15 Javascript
解析jQueryEasyUI的使用
Nov 22 Javascript
vue 中swiper的使用教程
May 22 Javascript
Vue弹出菜单功能的实现代码
Sep 12 Javascript
Layui弹框中数据表格中可双击选择一条数据的实现
May 06 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
咖啡常见的种类
2021/03/03 新手入门
Symfony2中被遗弃的getRequest()方法分析
2016/03/17 PHP
有效的捕获JavaScript焦点的方法小结
2009/10/08 Javascript
jQuery.Autocomplete实现自动完成功能(详解)
2010/07/13 Javascript
理解Javascript_03_javascript全局观
2010/10/11 Javascript
javascript对talbe进行动态添加、删除、验证实现代码
2012/03/29 Javascript
深入理解javascript学习笔记(一) 编写高质量代码
2012/08/09 Javascript
js获取触发事件元素在整个网页中的绝对坐标(示例代码)
2013/12/13 Javascript
jQuery消息提示框插件Tipso
2015/05/04 Javascript
jQuery实现的经典竖向伸缩菜单效果代码
2015/09/24 Javascript
jQuery prototype冲突的2种解决方法(附demo示例下载)
2016/01/21 Javascript
JS中substring与substr的用法
2016/11/16 Javascript
深入了解响应式React Native Echarts组件
2019/05/29 Javascript
[28:48]《真视界》- 2017年国际邀请赛
2017/09/27 DOTA
[01:16:12]完美世界DOTA2联赛PWL S2 FTD vs Inki 第一场 11.21
2020/11/23 DOTA
Python实现的插入排序算法原理与用法实例分析
2017/11/22 Python
Django中Middleware中的函数详解
2019/07/18 Python
python使用百度文字识别功能方法详解
2019/07/23 Python
Django3.0 异步通信初体验(小结)
2019/12/04 Python
python批量提取图片信息并保存的实现
2021/02/05 Python
Html5 页面适配iPhoneX(就是那么简单)
2019/09/05 HTML / CSS
迪梵英国官方网站:Darphin英国
2017/12/06 全球购物
理肤泉美国官网:La Roche-Posay
2018/01/17 全球购物
英国专业美容产品在线:Mylee(从指甲到脱毛)
2020/07/06 全球购物
库存图片、照片、矢量图、视频和音乐:Shutterstock
2021/02/12 全球购物
模范家庭事迹材料
2014/02/10 职场文书
辅导员评语
2014/05/04 职场文书
解除劳动合同协议书范本2014
2014/09/25 职场文书
购房协议书范本
2014/10/02 职场文书
2015年度残疾人工作总结
2015/05/14 职场文书
运动会闭幕式通讯稿
2015/07/18 职场文书
2015年全民创业工作总结
2015/07/23 职场文书
文案策划岗位个人自我评价(范文)
2019/08/08 职场文书
MySQL pt-slave-restart工具的使用简介
2021/04/07 MySQL
浅谈Redis中的RDB快照
2021/06/29 Redis
vue数据字典取键值项目的字典问题
2022/04/12 Vue.js