内容简介:多数本来写这篇文章只是想写写
多数 redux 初学者都会使用 redux-thunk 这个中间件来处理异步请求(比如我)
本来写这篇文章只是想写写 redux-thunk ,然后发现还不够,就顺便把 middleware 给过了一遍。
为什么叫 thunk ?
thunk 是一种包裹一些稍后执行的表达式的函数。
redux-thunk 源码
所有的代码就只有15行,我说的是真的。。 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-middleware是个啥
上图描述了一个 redux 中简单的同步数据流动的场景,点击 button 后, dispatch 一个 action ,reducer 收到 action 后,更新 state 后告诉 UI ,帮我重新渲染一下。
redux-middleware 就是让我们在 dispatch action 之后,在 action 到达 reducer 之前,再做一点微小的工作,比如打印一下日志什么的。试想一下,如果不用 middleware 要怎么做,最 navie 的方法就是每次在调用 store.dispatch(action) 的时候,都 console.log 一下 action 和 next State 。
store.dispatch(addTodo('Use Redux'));
复制代码
- 最
naive的方法,唉,每次都写上吧
const action = addTodo('Use Redux');
console.log('dispatching', action);
store.dispatch(action);
console.log('next state', store.getState());
复制代码
- 既然每次都差不多,那封装一下吧
function dispatchAndLog(store, action) {
console.log('dispatching', action);
store.dispatch(action);
console.log('next state', store.getState());
}
复制代码
- 现在问题来了,每次
dispatch的时候都要import这个函数进来,有点麻烦是不是,那怎么办呢?
既然 dispatch 是逃不走的,那就在这里动下手脚, redux 的 store 就是一个有几种方法的对象,那我们就简单修改一下 dispatch 方法。
const next = store.dispatch;
store.dispatch = function dispatchAndLog(action) {
console.log('dispatching', action);
next(action); // 之前是 `dispatch(action)`
console.log('next state', store.getState());
}
复制代码
这样一来我们无论在哪里 dispatch 一个 action ,都能实现想要的功能了,这就是中间件的雏形。
- 现在问题又来了,大佬要让你加一个功能咋办?比如要异常处理一下
接下来就是怎么加入多个中间件了。
function patchStoreToAddLogging(store) {
const next = store.dispatch
store.dispatch = function dispatchAndLog(action) {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
}
function patchStoreToAddCrashReporting(store) {
const next = store.dispatch
store.dispatch = function dispatchAndReportErrors(action) {
try {
return next(action)
} catch (err) {
console.error('Caught an exception!', err)
Raven.captureException(err, {
extra: {
action,
state: store.getState()
}
})
throw err
}
}
}
复制代码
patchStoreToAddLogging 和 patchStoreToAddCrashReporting 对 dispatch 进行了重写,依次调用这个两个函数之后,就能实现打印日志和异常处理的功能。
patchStoreToAddLogging(store) patchStoreToAddCrashReporting(store) 复制代码
- 之前我们写了一个函数来代替了
store.dispatch。如果直接返回一个新的dispatch函数呢?
function logger(store) {
const next = store.dispatch
// 之前:
// store.dispatch = function dispatchAndLog(action) {
return function dispatchAndLog(action) {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
}
复制代码
这样写的话我们就需要让 store.dispatch 等于这个新返回的函数,再另外写一个函数,把上面两个 middleware 连接起来。
function applyMiddlewareByMonkeypatching(store, middlewares) {
middlewares = middlewares.slice()
middlewares.reverse()
// Transform dispatch function with each middleware.
middlewares.forEach(middleware => (store.dispatch = middleware(store)))
}
复制代码
middleware(store) 会返回一个新的函数,赋值给 store.dispatch ,下一个 middleware 就能拿到一个的结果。
接下来就可以这样使用了,是不是优雅了一些。
applyMiddlewareByMonkeypatching(store, [logger, crashReporter]) 复制代码
我们为什么还要重写 dispatch 呢?当然啦,因为这样每个中间件都可以访问或者调用之前封装过的 store.dispatch ,不然下一个 middleware 就拿不到最新的 dispatch 了。
function logger(store) {
// Must point to the function returned by the previous middleware:
const next = store.dispatch
return function dispatchAndLog(action) {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
}
复制代码
连接 middleware 是很有必要的。
但是还有别的办法,通过柯里化的形式, middleware 把 dispatch 作为一个叫 next 的参数传入,而不是直接从 store 里拿。
function logger(store) {
return function wrapDispatchToAddLogging(next) {
return function dispatchAndLog(action) {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
}
}
复制代码
柯里化就是把接受多个参数的函数编程接受一个单一参数(注意是单一参数)的函数,并返回接受余下的参数且返回一个新的函数。
举个例子:
const sum = (a, b, c) => a + b + c; // Curring const sum = a => b => c => a + b + c; 复制代码
用 ES6 的箭头函数,看起来更加舒服。
const logger = store => next => action => {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
const crashReporter = store => next => action => {
try {
return next(action)
} catch (err) {
console.error('Caught an exception!', err)
Raven.captureException(err, {
extra: {
action,
state: store.getState()
}
})
throw err
}
}
复制代码
接下来我们就可以写一个 applyMiddleware 了。
// 注意:这是简单的实现
function applyMiddleware(store, middlewares) {
middlewares = middlewares.slice()
middlewares.reverse()
let dispatch = store.dispatch
middlewares.forEach(middleware => (dispatch = middleware(store)(dispatch)))
return Object.assign({}, store, { dispatch })
}
复制代码
上面的方法,不用立刻对 store.dispatch 赋值,而是赋值给一个变量 dispatch ,通过 dispatch = middleware(store)(dispatch) 来连接。
现在来看下 redux 中 applyMiddleware 是怎么实现的?
/**
* Composes single-argument functions from right to left. The rightmost
* function can take multiple arguments as it provides the signature for
* the resulting composite function.
*
* @param {...Function} funcs The functions to compose.
* @returns {Function} A function obtained by composing the argument functions
* from right to left. For example, compose(f, g, h) is identical to doing
* (...args) => f(g(h(...args))).
*/
// 就是把上一个函数的返回结果作为下一个函数的参数传入, compose(f, g, h)和(...args) => f(g(h(...args)))等效
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)))
}
复制代码
compose 最后返回的也是一个函数,接收一个参数 args 。
export default function applyMiddleware(...middlewares) {
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)
}
// 确保每个`middleware`都能访问到`getState`和`dispatch`
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// wrapDispatchToAddLogging(store)
dispatch = compose(...chain)(store.dispatch)
// wrapCrashReport(wrapDispatchToAddLogging(store.dispatch))
return {
...store,
dispatch
}
}
}
复制代码
google搜索redux-middleware第一张
到这里我们来看一下 applyMiddleware 是怎样在 createStore 中实现的。
export default function createStore(reducer, preloadedState, enhancer){
...
}
复制代码
createStore 接受三个参数: reducer , initialState , enhancer 。 enhancer 就是传入的 applyMiddleware 函数。
//在enhancer有效的情况下,createStore会返回enhancer(createStore)(reducer, preloadedState)。 return enhancer(createStore)(reducer, preloadedState) 复制代码
我们来看下刚刚的 applyMiddleware ,是不是一下子明白了呢。
return createStore => (...args) => {
// ....
}
复制代码
到这里应该就很容易理解 redux-thunk 的实现了,他做的事情就是判断 action 类型是否是函数,如果是就执行 action ,否则就继续传递 action 到下个 middleware 。
参考文档:
以上所述就是小编给大家介绍的《深入浅出redux-middleware》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 【1】JavaScript 基础深入——数据类型深入理解与总结
- 深入理解 Java 函数式编程,第 5 部分: 深入解析 Monad
- 深入理解 HTTPS
- 深入理解 HTTPS
- 深入浅出Disruptor
- 深入了解 JSONP
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Code Reading
Diomidis Spinellis / Addison-Wesley Professional / 2003-06-06 / USD 64.99
This book is a unique and essential reference that focuses upon the reading and comprehension of existing software code. While code reading is an important task faced by the vast majority of students,......一起来看看 《Code Reading》 这本书的介绍吧!