applyMiddleware
这是redux最出彩的地方,类似于koa的middleware。也是redux中最难懂的一部分。
一共有三种方式注入middleware。
// 1. const store = compose(applyMiddleware(logger, write))(createStore)(reducer)
// 2. const store = Redux.applyMiddleware(logger, write ...)(Redux.createStore)(reducer)
// 3. const store = Redux.createStore(reducer, null, applyMiddleware(logger, write))
// 拿第一种方式举例
const logger = store => next => action => {
console.log('logger: ', action);
next(action);
console.log('logger finish: ', action);
}
const write = store => next => action => {
console.log('write:', action);
next(action);
console.log('write finish:', action);
}
const finalCreateStore = Redux.compose(
Redux.applyMiddleware(logger, write)
)(Redux.createStore)
const store = finalCreateStore(reducer)
/*
=>
logger: { type: 'INCREMENT' }
write: { type: 'INCREMENT' }
...
write finish: { type: 'INCREMENT' }
logger finish: { type: 'INCREMENT' }
*/
applyMiddleware是三级柯里化函数。需要传递中间件列表->store->createStore参数。直接看代码。
applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState, enhancer) => {
var store = createStore(reducer, preloadedState, enhancer)
var dispatch = store.dispatch
var chain = []
var middlewareAPI = {
getState: store.getState,
// 这样中间件拿到store 可以再次 dispatch
dispatch: (action) => dispatch(action)
}
// middlewareAPI作为参数执行中间件最外层。得到中间件 返回数组,既为[next => action => {...}]
chain = middlewares.map(middleware => middleware(middlewareAPI))
// 注意compose是逆序,也就是说,store.dispatch传递给最后一个middleware的next,执行完之后,把最后一个中间件返回给倒数第二个,next是最后一个...以此类推,最后返回第一个中间件,next为第二个中间件。
// 执行的时候,先执行第一个中间件,next执行第二个...以此类推,最后执行store.dispatch.
dispatch = compose(...chain)(store.dispatch)
// 中间件依次执行。中间件任意位置可以自己手动执行next(包括初始化部分)
return {
...store,
dispatch
}
}
}
先看这段代码:
var middlewareAPI = {
getState: store.getState,
// 这样中间件拿到store 可以再次 dispatch
dispatch: (action) => dispatch(action)
}
// middlewareAPI作为参数执行中间件最外层。得到中间件 返回数组,既为next => action => {...}
chain = middlewares.map(middleware => middleware(middlewareAPI))
middlewareAPI作为参数传递给每个middleware的store中,但是只有两个函数(还记得之前说过store返回4个函数么?)。中间件里可以拿到store做getState,也可以再次dispatch。
接下来,依次执行每个中间件,并且把返回结果赋给dispatch。不熟悉compose的同学可以单独参考我之前写的函数式编程之compose。
dispatch = compose(...chain)(store.dispatch)
注意compose是逆序,也就是说,store.dispatch传递给最后一个middleware的next,执行完之后,把最后一个中间件返回给倒数第二个,next是最后一个...以此类推,最后返回第一个中间件,next为第二个中间件。
执行的时候,先执行第一个中间件,next执行第二个...以此类推,最后执行store.dispatch。
就拿第一个例子来说,先执行logger,write,dispatch,write end ,logger end。
这样,再次dispatch时候,会依次执行每个中间件。顺如如上所说。
redux-thunk
如果你使用redux中,遇到需要异步的事件怎么办?比如你想请求后台,根据后台返回的结果刷新表格。
在redux,dispatch中的action只能返回一个object对象的。我们可以在dispatch源码中看到这样一句话
function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
...
也就是说,dispatch会把这个action当做reducer的数据传递下去,根本不给我们任何异步请求的机会。
这里我们必须使用中间件,可以手写,也可以使用redux-thunk或者redux-promise等中间件。
我们先来看看如何手写,还记得这段代码吗?
var middlewareAPI = {
getState: store.getState,
// 这样中间件拿到store 可以再次 dispatch
dispatch: (action) => dispatch(action)
}
中间件的一阶函数柯里化函数的参数中,可以拿到dispatch函数,我们可以再次dispatch。OK,思路来了,我们完全可以等异步函数执行完毕再次dispatch嘛。
那么我们如何知道异步函数何时执行完呢?这个问题很好解决,给异步让用户去处理,把dispatch传递给用户,用户处理完了直接调用dispatch就好了。代码如下: 源码
const thunk = ({ dispatch }) => next => action => {
// action必须是个函数,而不能是object对象
if (typeof action === 'function') {
// 把dispatch传递给用户,让用户自己处理
return action(dispatch);
}
return next(action);
}
这样,我们的ActionCreator中的返回必须是个函数,而不是之前的action(因为中间件判断action是函数才会有特殊处理,见上)。
使用的地方就很简单了。如上所说ActionCreator直接返回一个函数,函数的参数是dispatch,函数里做异步请求,请求完毕后dispatch即可。
export function addTodoAsync(text) {
return dispatch => {
// 模拟异步请求
setTimeout(() => {
dispatch(addTodo(text));
}, 1000);
}
}
我们再来看看redux-thunk的源码,和我们的代码如出一辙。谁抄谁的我就不吹牛了,我想读者一定心知肚明。
redux-promise
既然 ActionCreator可以返回函数,当然也可以返回其他值。另一种异步操作的解决方案,就是让 ActionCreator返回一个 Promise 对象。 源码
const promise = ({ dispatch }) => next => action => {
function isPromise(val) {
return val && typeof val.then === 'function';
}
// 如果action是异步函数,则dispatch后直接返回数据,当然数据需要经过ActionCreate包装处理
return isPromise(action)
? action.then(dispatch)
: next(action);
return next(action)
}
使用时候,可以这么使用
export function addTodoFetch(dispatch, postTitle) {
return fetch(`/api/v1/getText.json?text=${text}`).then(response => {
type: 'ADD_TODO',
payload: response.json()
})
}
代码非常简单明确,如果action是promise函数,那么直接调用then,把上次的返回值传递给dispatch执行。也就是说,这个promise的回调应该返回一个action,或者说,这个回调就是个接收response的ActionCreator。
我们再来看看redux-promise是如何实现的。
import { isFSA } from 'flux-standard-action';
function isPromise(val) {
return val && typeof val.then === 'function';
}
export default function promiseMiddleware({ dispatch }) {
return next => action => {
if (!isFSA(action)) {
return isPromise(action)
? action.then(dispatch)
: next(action);
}
return isPromise(action.payload)
? action.payload.then(
result => dispatch({ ...action, payload: result }),
error => {
dispatch({ ...action, payload: error, error: true });
return Promise.reject(error);
}
)
: next(action);
};
}
从上面代码可以看出,如果 ActionCreator 返回的action是一个 Promise,它resolve 以后的值应该是一个 Action 对象,会被dispatch当做事件执行(action.then(dispatch))(注意,reject 以后不会有任何动作)。如果 action对象的payload属性是一个Promise对象,那么无论 resolve 和 reject,dispatch方法都会发出action,失败后会多一个error属性,payload也会变成失败信息。