前言
刚来公司的时候,对react项目中的thunk中间件的作用一直不太了解,最近有时间决定好好研究一下。鉴于本人初次写博客,并已假设读者已掌握redux的一些基本用法;如有错误,还望指出。不胜感激!
首先简单回顾一下redux工作流程
图画的不太好,见谅;
对于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 }) "_blank" href="https://redux.js.org/basics/actions" rel="external nofollow" >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要进行状态更新。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
react-thunk,中间件
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新动态
- 《庄心妍 实力唱将 发烧音乐精选集》[WAV+CUE][380MB]
- [音展纪念碟]2006年《TAA台湾第十六届国际HI-ENDHI-FI音响展》2cd[WAV]
- 中国音乐地图之听见西藏山歌牧歌酒歌2020[WAV分轨]
- 王宏伟2017-永远的红飘带[太平洋影音][WAV+CUE]
- 费翔.1997-NOW现在【第一中国】【WAV+CUE】
- 新格合唱团.1981-我唱你和2辑(滚石新格民歌系列)【新格】【WAV+CUE】
- 陈柏宇.2011-QUINQUENNIUM【SONY】【WAV+CUE】
- 雨果唱片-《金唛系列-蔷薇依韵琼瑶》WAV
- 陈百强《凝望(2012华纳LPCD45限量版)[DMI]》[WAV整轨]
- 模拟之声慢刻CD《徐小凤45周年》[德国母带][低速原抓WAV+CUE]
- 《庄心妍 实力唱将 发烧音乐精选集》[WAV+CUE][380MB]
- 《王菲 单曲合集》[FlAC/分轨][520MB]
- 《费玉清 原味再现 水长流》[WAV+CUE][430MB]
- 群星.1992-畅销榜老歌金曲10CD【风格】【WAV+CUE】
- 孙悦.2024-时光音乐会【金蜂】【WAV+CUE】