Open willson-wang opened 3 years ago
redux 的 middleware 是为了增强 dispatch 而出现的
没有middleware
有middleware
那么redux中间件是怎样实现的?
我首先想到的是redux-thunk,因为这个最简单,也最好理解,它是怎么做到支持传入disptach方法的参数可以是函数,然后看了下源码实现,只有短短几行代码,如下所示
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;
实现逻辑就是对传入的action做判断,如果传入的action是函数,则调用action,把dispatch、getState在传入到action函数内,方便action函数内处理完副作用之后,可以执行dispatch,也可以通过getState获取到最新的store内容
action.js // 获取用户信息 export function getUserInfo(params) { return async function(dispatch, getState) { const { app } = getState() const { userInfo } = app if (Object.keys(userInfo).length && !params._force) { return userInfo } const user = await authorize.getUserInfoPromise() dispatch({ type: types.UPDATEUSERINFO, payload: user }) return { userInfo: user } } } index.js const mapDispathToProps = (dispath: Dispatch): Actions.AppActionsMethodTypes => { return { getUserInfo: (parmas) => { // dispatch传入的action是一个函数 return dispath(Actions.getUserInfo(parmas)) } } } this.props.getUserInfo({}) redux-thunk.js if (typeof action === 'function') { return action(dispatch, getState, extraArgument); }
显然redux-thunk是通过一种劫持的手段,来支持了传入的action是function的场景
那么我们在来看下为什么要 return next(action); 这个next是什么?redux中间件说的洋葱模型又是什么?
先看下redux中间件的书写及调用方式,以redux-thunk为例
redux-tunk.js 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; store.js const middlewares = [thunk] const middlewareEnhancer = applyMiddleware(...middlewares) const store = createStore(createReducer(), preloadState, middlewareEnhancer) return store
调用applyMiddleware方法传入middlewares参数
看下applyMiddleware的源码实现
export default function applyMiddleware( ...middlewares ){ return (createStore) => ( reducer, preloadedState ) => { const store = createStore(reducer, preloadedState) let dispatch: Dispatch = () => { throw new Error( 'Dispatching while constructing your middleware is not allowed. ' + 'Other middleware would not be applied to this dispatch.' ) } const middlewareAPI: MiddlewareAPI = { getState: store.getState, dispatch: (action, ...args) => dispatch(action, ...args) } const chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
applyMiddleware接受一个middlewares数组,然后返回一个函数,这个函数接受一个createStore参数,其实这就是一个闭包
然后我们在看下
createStore(createReducer(), preloadState, middlewareEnhancer) redux.js function createStore(reducer, preloadedState, enhancer) { if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { throw new Error( `Expected the enhancer to be a function. Instead, received: '${kindOf( enhancer )}'` ) } return enhancer(createStore)(reducer,preloadedState) } } 这里判断是否传入了enhancer函数,也就是传入了applyMiddleware函数的返回值
回过头来看这里
我们在项目代码内调用
const store = createStore(createReducer(), preloadState, middlewareEnhancer) return store <Provider store={store}> <Root /> </Provider> 这个store则是applyMiddleware内最终反回的 return { ...store, dispatch }
然后我们细看下applyMiddleware方法内的实现
const store = createStore(reducer, preloadedState) let dispatch = () => { throw new Error( 'Dispatching while constructing your middleware is not allowed. ' + 'Other middleware would not be applied to this dispatch.' ) } const middlewareAPI: MiddlewareAPI = { getState: store.getState, dispatch: (action, ...args) => dispatch(action, ...args) } // 关键步骤一,往每个中间件函数传入getState,及dispatch这两个方法 const chain = middlewares.map(middleware => middleware(middlewareAPI)) // 关键步骤二,通过compose组合调用中间件函数,然后传入原始的store.dispatch方法 dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch }
加入我们传入三个中间件fn1, fn2,fn3先看下chain得到的是什么
middlewares数组应该是这样的 [ // 三层函数的中间件 ({getState, dispatch}) => next1 => action => next1(action), ({getState, dispatch}) => next2 => action => next2(action), ({getState, dispatch}) => next3 => action => next3(action) ] const chain = middlewares.map(middleware => middleware(middlewareAPI)) // 传入middlewareAPI之后,得到的chain应用事这样的数组 [ // 2层函数的中间件了,此时已经可以去访问调用dispatch与getState变量了 next1 => action => next1(action), next2 => action => next2(action), next3 => action => next3(action) ]
我们看下compose的实现
const compose = (...fns) => { if (!fns.length) { return (arg) => arg } if (fns.length === 1) { return fns[0] } return fns.reduce((a, b) => { return (...args) => { return a(b(...args)) } }) }
compose(fn1, fn2, fn3) => 最终返回的事一个函数 (...args) => {return a(b(...args))}
compose(fn1, fn2, fn3) =>
(...args) => {return a(b(...args))}
我们用个例子来试下
function fn1(...args) { console.log('fn1', ...args) return 1 } function fn2(...args) { console.log('fn2', ...args) return 2 } function fn3(...args) { console.log('fn3', ...args) return 3 } 我们传入一个函数, const result1 = compose(fn1) result1(999) 打印结果:fn1 999 传入两个函数,会遍历一次 const result2 = compose(fn1, fn2) result2(999) 打印结果:fn2 999; fn1 2; 1 我们传入三个函数,会遍历两次 const result3 = compose(fn1, fn2, fn3) result3(999) 打印结果:fn3 999; fn2 3; fn1 2; 1 所以compose(fn1, fn2, fn3) === (...args) => fn1(fn2(fn3(...args))) compose的作用:从右至左依次执行函数,将上一个函数的执行结果传入到下一个函数 // 在来看这行代码 dispatch = compose(...chain)(store.dispatch) 拆开来看 第一步 const composeMiddleware = compose(...chain) composeMiddleware的值事这样的 (...args) => fn1(fn2(fn3(...args))) 第二步 dispatch = composeMiddleware(store.dispatch) dispatch的值如下所示fn1(fn2(fn3(store.dispatch))),也就是第一个中间件函数最终返回的值,通过前面的步骤我们可以知道chain数组如下所示 [ next1 => action => next1(action), next2 => action => next2(action), next3 => action => next3(action) ] 那么fn1(fn2(fn3(store.dispatch)))调用过程如下所示 fn3(store.dispatch)调用 传入参数 next3(store.dispatch) 返回值为action => store.dispatch(action) fn2(fn3调用返回参数)调用 传入参数 next2(action => store.dispatch(action)) 返回值为action => (action => store.dispatch(action))(action) fn1(fn2调用返回参数)调用 传入参数 next1(action => (action => store.dispatch(action))(action)) 返回值为 (action => (action => store.dispatch(action))(action))(action) 调用完之后的返回值是fn1 next => action => next(action) 调用的返回值 action => next(action), 这个next就是fn2调用返回的值 dispatch = action => next1(action) (也就是fn1的返回值)
实际上将每个fn函数都执行一边,第一个执行的函数,也就是最右边的函数,会接受到一个store.dispatch函数,后面的函数接受到的都是上一个函数执行的返回结果
我们看下洋葱模型,定义三个中间件logger1、logger2、logger3
function logger1(...args) { // 这一层函数的目的,可以帮助中间件定义的时候传入一些自定义参数 return function({dispatch, getState}) { // 这一层函数的目的是,接受getState,dispatch函数,可以拿到store内容,并做一些操作 return function (next) { // 接受store.dispatch参数或者下一个中间件的返回函数 return function (action) { console.log('logger1 enter', action) const result = next(action) console.log('logger1 outer', result) return result } } } } const log1 = logger1() function logger2(...args) { return function({dispatch, getState}) { return function (next) { return function (action) { console.log('logger2 enter', action) const result = next(action) console.log('logger2 outer', result) return result } } } } const log2 = logger2() function logger3(...args) { return function({dispatch, getState}) { return function (next) { return function (action) { console.log('logger3 enter', action) const result = next(action) console.log('logger3 outer', result) return result } } } } const log3 = logger3() const obj = { getState: () => {}, dispatch: () => {} } const chain = [log1(obj), log2(obj), log3(obj)] var dispatch = (...args) => { console.log('原始 dispatch') return args } const newDispatch = compose1(...chain)(dispatch) console.log('last', newDispatch) newDispatch({ type: 'INIT', payload: { a: 123 } }) // 执行结果为 // logger1 enter {type: "INIT", payload: {…}} // logger2 enter {type: "INIT", payload: {…}} // logger3 enter {type: "INIT", payload: {…}} // 原始 dispatch // logger3 outer {type: "INIT", payload: {…}} // logger2 outer {type: "INIT", payload: {…}} // logger1 outer {type: "INIT", payload: {…}}
这个就是洋葱模型,如下图所示
到这里我们已经知道,redux的中间件是怎么去设计的了,同时也知道中间件为什么需要定义4层函数,同时我们在看之前的一个细节
// 这里定义了一次dispatch函数 let dispatch: Dispatch = () => { throw new Error( 'Dispatching while constructing your middleware is not allowed. ' + 'Other middleware would not be applied to this dispatch.' ) } const middlewareAPI: MiddlewareAPI = { getState: store.getState, dispatch: (action, ...args) => dispatch(action, ...args) // 这里传入的dispatch方法,内部调用的就是上面定义的dispatch方法,这里只所以没有直接传入store.dispatch,就是为了避免中间件在初始化的时候,或者执行的时候去修改了原始的dispatch方法 } const chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch)
总体来说,中间件的实现的关键思路是使用compose组合的方式,将一系列函数组合成如下函数(...args) => fn1(fn2(fn3(...args))),即将最后一个函数调用的返回值当成参数传入到前一个函数,当第一个函数被调用的时候,依次调用传入的参数,直到把传入的参数传入最后一个函数;
middleware
redux 的 middleware 是为了增强 dispatch 而出现的
没有middleware
有middleware
那么redux中间件是怎样实现的?
我首先想到的是redux-thunk,因为这个最简单,也最好理解,它是怎么做到支持传入disptach方法的参数可以是函数,然后看了下源码实现,只有短短几行代码,如下所示
实现逻辑就是对传入的action做判断,如果传入的action是函数,则调用action,把dispatch、getState在传入到action函数内,方便action函数内处理完副作用之后,可以执行dispatch,也可以通过getState获取到最新的store内容
显然redux-thunk是通过一种劫持的手段,来支持了传入的action是function的场景
那么我们在来看下为什么要 return next(action); 这个next是什么?redux中间件说的洋葱模型又是什么?
先看下redux中间件的书写及调用方式,以redux-thunk为例
调用applyMiddleware方法传入middlewares参数
看下applyMiddleware的源码实现
applyMiddleware接受一个middlewares数组,然后返回一个函数,这个函数接受一个createStore参数,其实这就是一个闭包
然后我们在看下
回过头来看这里
我们在项目代码内调用
然后我们细看下applyMiddleware方法内的实现
加入我们传入三个中间件fn1, fn2,fn3先看下chain得到的是什么
我们看下compose的实现
compose(fn1, fn2, fn3) =>
最终返回的事一个函数(...args) => {return a(b(...args))}
我们用个例子来试下
实际上将每个fn函数都执行一边,第一个执行的函数,也就是最右边的函数,会接受到一个store.dispatch函数,后面的函数接受到的都是上一个函数执行的返回结果
我们看下洋葱模型,定义三个中间件logger1、logger2、logger3
这个就是洋葱模型,如下图所示
到这里我们已经知道,redux的中间件是怎么去设计的了,同时也知道中间件为什么需要定义4层函数,同时我们在看之前的一个细节
总体来说,中间件的实现的关键思路是使用compose组合的方式,将一系列函数组合成如下函数(...args) => fn1(fn2(fn3(...args))),即将最后一个函数调用的返回值当成参数传入到前一个函数,当第一个函数被调用的时候,依次调用传入的参数,直到把传入的参数传入最后一个函数;
middleware