中间件的内容其实不属于React
源码相关,属于Redux
相关。可是中间件的原理是一个很是重要的知识点,它是咱们前端开发解决一些业务问题时的利器。不少前端应用的架构都是使用中间件为基础搭建的。前端
本文不会介绍Redux
相关的内容,只关注于中间件的实现原理。react
本文将是我学习react
源码的目前阶段的最后一篇文章,react
源码内容比较多,也比较晦涩,我也不可以一蹴而就。等过段时间再继续深刻学习的时候,再来接着更新这一系列的内容。git
在React
开发中,管理数据状态的Redux
是每一个人都会接触到的内容(如同Vuex
在Vue
开发当中的地位)。咱们知道,在Redux
中,咱们想要修改数据,咱们必须先派发一个Action
,Action
会被Reducer
读取,Reducer
将根据Action
内容的不一样执行不一样的计算逻辑,最终生成新的state
,这个新的state
会更新到Store
对象里,进而驱动视图层面做出对应的改变。github
这里有一个须要注意的地方,Redux
源码中只有同步操做,也就是说当咱们dispatch action
时,state
会被当即更新。编程
若是咱们想引入异步数据流,该怎么办?官方的建议就是使用中间件。redux
本文使用redux-thunk
做为处理异步数据流的方式。源码地址数组
import thunkMiddleware from 'redux-thunk'
import reducer from './reducers'
// 使用redux-thunk中间件
const store = createStore(reducer, applyMiddleware(thunkMiddleware))
复制代码
这样配置以后,咱们就能够给dispatch
(这个dispatch
并不是原始的dispatch
)传入一个函数,并能够在该函数中使用异步数据流。markdown
thunk
的源码很简单,也就10多行代码架构
function createThunkMiddleware(extraArgument) {
// 返回值是一个 thunk,它是一个函数
return ({ dispatch, getState }) => (next) => (action) => {
// thunk 若感知到 action 是一个函数,就会执行 action
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
// 若 action 不是一个函数,则不处理,直接放过
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
复制代码
在这里先简单的分析一下它的源码。首先这个方法返回是一个三重的函数,第一重接收的是一个dispatch
(组合中间件出来的一个链)和getState
(store.getState
获取state
数据的方法)的对象,第二重是next
(store.dispatch
最原始的dispatch
方法),第三重是action
(这个方法是一个action
对象或者是一个异步的函数)。app
这里的内容可能一会儿没法理解,不要紧,接下来咱们配合Redux
的applyMiddleware
的方法流程,一步步来剖析Redux
的中间件原理。
咱们先看看applyMiddleware
的源码。
// applyMiddlerware 会使用“...”运算符将入参收敛为一个数组
export default function applyMiddleware(...middlewares) {
// 它返回的是一个接收 createStore 为入参的函数
return createStore => (...args) => {
// 首先调用 createStore,建立一个 store
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.`
)
}
// middlewareAPI 是中间件的入参
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
// 遍历中间件数组,调用每一个中间件,而且传入 middlewareAPI 做为入参,获得目标函数数组 chain
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 改写原有的 dispatch:将 chain 中的函数按照顺序“组合”起来,调用最终组合出来的函数,传入 dispatch 做为入参
dispatch = compose(...chain)(store.dispatch)
// 返回一个新的 store 对象,这个 store 对象的 dispatch 已经被改写过了
return {
...store,
dispatch
}
}
}
复制代码
由于applyMiddleware
是在createStore
当中使用的,因此咱们也须要看一部分的createStore
源码。
function createStore(reducer, preloadedState, enhancer) {
// 这里处理的是没有设定初始状态的状况,也就是第一个参数和第二个参数都传 function 的状况
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
// 此时第二个参数会被认为是 enhancer(中间件)
enhancer = preloadedState;
preloadedState = undefined;
}
// 当 enhancer 不为空时,便会将原来的 createStore 做为参数传入到 enhancer 中
if (typeof enhancer !== 'undefined') {
return enhancer(createStore)(reducer, preloadedState);
}
......
}
复制代码
createStore
对于applyMiddleware
的使用逻辑比较简单其实主要就是一句话
return enhancer(createStore)(reducer, preloadedState);
复制代码
咱们把这几个参数,代入applyMiddleware
中再去看。
// middlewares是传入的中间件
export default function applyMiddleware(...middlewares) {
// 它返回的是一个接收 createStore 为入参的函数
return createStore => (...args) => {
......
}
}
复制代码
applyMiddleware
方法中的createStore
,其实就是Redux
中的createStore
方法,而args
则对应的是reducer
、preloadedState
,这两个参数均为createStore
函数的约定入参。
咱们接着来看下面的内容
// 首先调用 createStore,建立一个 store
const store = createStore(...args)
// 用来防止在遍历 middleware 时调用dispatch
let dispatch = () => {
throw new Error(
`Dispatching while constructing your middleware is not allowed. ` +
`Other middleware would not be applied to this dispatch.`
)
}
// middlewareAPI 是中间件的入参
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
// 遍历中间件数组,调用每一个中间件,而且传入 middlewareAPI 做为入参,获得目标函数数组 chain
const chain = middlewares.map(middleware => middleware(middlewareAPI))
复制代码
这里的内容就是调用createStore
建立一个store
。而后建立一个middlewareAPI
对象,遍历middleware
(中间件),并传入middlewareAPI
参数。这里其实就是以前thunk
第一重函数接收的dispatch
和getState
。
而后看下一句
dispatch = compose(...chain)(store.dispatch)
复制代码
这里的compose(...chain)
咱们下面的章节再看,这里能够先把这个方法当作以下
dispatch = middleware(middlewareAPI)(store.dispatch)
复制代码
这里正好对于thunk
的第二重函数接收next
参数。这里要注意一点,dispatch
已经被重写。
return {
...store,
dispatch
}
复制代码
最后返回一个重写过dispatch
的对象,这个对象会被绑定到咱们的页面上,好比
<Provider store={store}>
<App />
</Provider>
复制代码
而后咱们经过useDispatch
拿到的dispatch
其实就是thunk
中间件改写过的方法。咱们用这个dispatch
去传入action
对象或者异步数据流函数,其实就是调用thunk
的第三重函数
(action) => {
// thunk 若感知到 action 是一个函数,就会执行 action
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
// 若 action 不是一个函数,则直接使用dispatch派发action
return next(action);
};
复制代码
注意一点,这里的dispatch
已是被改写的dispatch = compose(...chain)(store.dispatch)
。
这样,咱们就经过中间件实现了Redux
的异步数据流。咱们就能够给dispatch
传入一个函数,来异步的派发action
。
compose
是一个函数式编程中,很经常使用的工具方法。它的功能就是把多个函数组合起来。
在redux
中如有多个中间件,那么redux
会结合它们被“安装”的前后顺序,依序调用这些中间件。因此,咱们须要使用compose
方法把中间件函数组合起来。
注意:compose
只是一个函数式编程的思路,实现方式有不少种,下面只是redux
中实现的一种
port default function compose(...funcs) {
// 处理数组为空的边界状况
if (funcs.length === 0) {
return arg => arg
}
// 若只有一个函数,也就谈不上组合,直接返回
if (funcs.length === 1) {
return funcs[0]
}
// 如有多个函数,那么调用 reduce 方法来实现函数的组合
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
compose(f1, f2, f3, f4);
// => 会转换成下面这种形式
(...args) => f1(f2(f3(f4(...args))));
复制代码
多个中间件嵌套的时候,这里的思路可能会有点绕,我举个例子来讲明一下。假设咱们使用了两个中间件,一个是thunk
,一个是redux-logger
(当redux
数据变动的时候,会打印相关信息到控制台)。源码地址
// dispatch定义
dispatch = thunk(createLogger(store.dispatch));
// dispatch被使用
dispatch(action);
//等同于
thunk(createLogger(store.dispatch))(action)
复制代码
咱们来看下redux-logger
的源码,若是我只保留与redux
中间件相关的逻辑的话,它的源码能够压缩成几行代码。
return ({ getState }) => next => (action) => {
return next(action);
};
复制代码
这里也有三重的代码,第一重在咱们遍历中间件数组的时候就被调用了
const chain = middlewares.map(middleware => middleware(middlewareAPI));
复制代码
因此这里传入thunk
的应该是第二重的代码
thunk(createLogger(store.dispatch))(action)
// 等同于
thunk((action) => store.dispatch(action))(action)
复制代码
咱们再来看看以前thunk
中间件的源码
function createThunkMiddleware(extraArgument) {
// 返回值是一个 thunk,它是一个函数
return ({ dispatch, getState }) => (next) => (action) => {
// thunk 若感知到 action 是一个函数,就会执行 action
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
// 若 action 不是一个函数,则不处理,直接放过
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
复制代码
咱们再次来精简代码
thunk((action) => store.dispatch(action))(action)
// 等同于
const thunk = (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
thunk((action) => store.dispatch(action))(action);
// 等同于
const thunk = (action) => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return store.dispatch(action);
};
thunk(action);
复制代码
这样,就实现了多个中间件的依次调用。
若是本文对你有所帮助,请帮忙点个赞,感谢!