Redux解决了react中出现的多交互、多数据源问题,但是如果有异步操作,或者要对操作进行拦截或者执行后回调就比较麻烦。于是我们需要Redux 中间件。
一、手动增强store.dispatch
我们知道 react-redux的 connect是一个高阶组件,它将组件包装之后拿到一个能够在组件中直接获取 context 的 state 的组件,并且用dispatch监听每一个action:
export const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {
class Connect extends Component {
...
let dispatchProps = mapDispatchToProps
? mapDispatchToProps(store.dispatch, this.props)
: {} // 用来 dispatch 的时候获取 store 的 dispatch
...
render() {
return <WrappedComponent {...this.state.allProps}/>
}
}
return Connect;
}
如果要增强dispatch,我们可以对其进行重构,直接改写 store 实例中的 dispatch:
let store = createStore(rootReducer);
let dispatch = store.dispatch//拿到dispatch
store.dispatch = function (action) {//对dispatch进行重构
console.log('旧状态',store.getState())//1.打印就状态
dispatch(action)//2.在执行之前的action
console.log('新状态',store.getState())//3.打印新状态
}
以上的代码增强了dispatch方法,使执行顺序变成了action->log->reducer->log
,执行结果如下:
二、使用redux的applyMiddleware方法增强store.dispatch
redux 提供了类似后端 Express 的中间件概念,本质的目的是提供第三方插件的模式,自定义拦截 action -> reducer
的过程。变为 action -> middlewares -> reducer
。这种机制可以让我们改变数据流,实现如异步 action ,action 过滤,日志输出,异常报告等功能。
官方说明如下:使用中间件扩展增强Redux store上的
dispath
方法。因为中间件可能是异步的,所以这应该是定义在组合链
中存储增强器。
export default function applyMiddleware(...middlewares) {//[middleware1,middleware2]
return createStore => (...args) => {
const store = createStore(...args)
let dispatch = () => {
throw new Error(
`Dispatching while constructing your middleware is not allowed. ` +
`Other middleware would not be applied to this dispatch.`
)
}
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))//将middleware放入链式数组
dispatch = compose(...chain)(store.dispatch)//依次执行
return {// 将增强过的dispatch返回
...store,
dispatch
}
}
}
applyMiddleware的使用方法,官方文档中给出两种
用法:
const store = createStore(reducer, preloadedState, applyMiddleware(...))
const store = createStore(reducer, applyMiddleware(...))
第二个参数初始化state不是必传的,源码中的createStore方法对参数进行了处理
三、middleware如何工作
export default function createStore(reducer, preloadedState, enhancer) {
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)
}
......
}
在createStore方法中判断参数并相应替换,最后createStore代码执行返回的是一个enhancer函数嵌套调用方法,也就是:
const store = applyMiddleware(...)(createStore)(reducer,preloadedState)
如图所示:嵌套函数分别传入的createStore和reducer,创建了store
,并定义了dispatch方法,并组合成obj传给了logger函数
logger中间件函数接受两个参数 dispatch getState
(获取状态 派发动作) 并返回一个新的参数 next
,形成了一个dispatch增强函数。小白我对于这个一长串的return理解成如下:
let logger1 = function({dispatch,getState}) {
store.dispatch = function(action){
console.log('旧状态1',getState())
next(action)
console.log('新状态1',getState())
}
}
这已经跟文章开头手动增强store.dispatch的函数十分相近了,主要区别在于next方法。
middleware 通过 next(action) 一层一层处理和传递 action
,直到 redux 原生的 dispatch`,这时next为客户端调用的dispatch方法,action为方法传入的actionType:{type:xxx,payload:xxx}
咳,代码要优雅,小白我理解了就要按照官方的来,正确的middleWare一样定义格式如下:
let logger = ({dispatch,getState}) => next => action =>{
console.log('旧状态1',getState())
next(action)//dispatch(action)
console.log('新状态1',getState())
}
中间件的实现和洋葱模型很像,先触发logger第一层,再触发dispatch事件,最后再从logger函数出来。
四、compose实现链式调用
实现多个中间件先后调用的关键是compose函数
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
源码很精简,理解有点复杂。其实是使用reduce不断将最右
先调动函数的结果返回给后调用的函数。
举个栗子,
function add1(str){
return str+1;
}
function add2(str){
return str+2;
}
function add3(str){
return str+3;
}
let add = compose(add3,add2,add1);
let r = add("啊哈哈哈")//啊哈哈哈123
在这段代码中,compose函数执行顺序为add1->add2->add3,并将结果作为参数
传给下一个函数。
在redux中当新 dispatch 执行时,[f1, f2, ... , fx, ..., fn],从右到左依次执行。
dispatch = f1(f2(f3(store.dispatch))))
如图所示,从右至左执行logger2,logger1。logger2返回的代码作为参数传给logger1的next参数。按照图上1->2(执行下一个middleware)->3->4(触发redux 原生的 dispatch方法)->5->6 完成
链式middleware流程图如下
五、异步操作
很多时候,我们需要异步操作。用户触发第一个dispatch事件的action,需要发送第二个action。或者根据返回的根据发送第二个处理请求。
解决异步操作的方法:
(1)redux函数的参数是dispatch和getState,把返回的obj改成返回一个异步函数。
(2)异步操作结束之后,再发出一个 Action。
function incrementAsync() {
return dispatch => {
setTimeout(() => {
// Yay! Can invoke sync or async actions with `dispatch`
dispatch(increment());
}, 1000);
};
}
这样子能理想得解决异步操作,而store.dispatch方法正常情况下,参数只能是对象,不能是函数。
这个时候可以引入redux-thunk
Redux Thunk middleware allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met. The inner function receives the store methods dispatch and getState as parameters.
redux-thunk实现源码:
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
redux-thunk 做的事情就是判断 action 类型是否是函数,若是,则执行 action,若不是,则继续传递 action 到下个 middleware。
运用方法:
let store = createStore(
rootReducer,
applyMiddleware(thunk,logger1,logger2)
)
如图所示,当store.dispatch运行到thunk中间件,发现返回的是一个function,则执行返回的函数,并返回,重新派发dispatch
。
因此使用redux-thunk,改造store.dispatch。可以实现异步方法
还有如 redux-promise redux-saga也可以解决异步的问题
招聘贴
字节跳动招人啦!
职位描述:前端开发(高级)ToB方向—视频云(Base: 上海、北京)
1、负责音视频点播/直播/实时通信等多媒体服务产品化以及业务云平台建设;
2、负责多媒体质量体系、运维体系建设及系统开发工作;
3、擅长抽象设计、工程化思维,专注交互、打造极致用户体验。
职位要求
1、计算机、通信和电子信息科学等相关专业优先;
2、熟练掌握各种前端技术,包括 HTML/CSS/JavaScript/Node.js 等;
3、深入了解 JavaScript 语言,使用过 React 或 Vue.js 等主流开发框架;
4、熟悉 Node.js,了解 Express/KOA 等框架,有大型服务端程序开发经验者优先;
5、对用户体验、交互操作及用户需求分析等有一定了解,有产品或界面设计经验者优先;
6、有自己的技术产品、开源作品或活跃的开源社区贡献者优先。
职位亮点
视频云团队依托抖音、西瓜视频等产品的音视频技术积累和基础资源,为客户提供极致的一站式音视频多媒体服务,包括音视频点播、直播、实时通信、图片处理等。对内作为视频技术中台,服务内部业务;对外打造产品化的音视频多媒体服务解决方案,服务企业级用户。
团队具备规范的项目迭代流程、完善的项目角色配置;技术氛围浓厚,拥抱开源社区,定期分享,让大家能够伴随业务快速成长,用技术改变世界!
投递方式
可直接发送简历至:yuanyuan.wallace@bytedance.com
也可以扫描内推二维码在线投递,期待你的加入!~