背景 本以为对javascript
掌握还算可以,但是当看了Redux
库的applyMiddleware
(中间件)的代码后,我恍然地认识到了javascript
是可以这么灵活,js 下的函数式编程
是这么的美妙呀~。
每每看到这段二十来行的代码,都会细品。。。(还是因为自己菜看不懂)。
applyMiddleware 的作用
中间件之所以是中间件,表面上的dispatch(action)
,实际上中间件已经将dispatch
的 action
重重处理,最终再经手最“原始”的dispatch
,并交给Reducer
,总而言之,中间件增强了dispatch
的功能,让其不仅可以处理异步(redux-thunk/redux-saga),还可以实现redux-logger这样的功能。一张图解释中间的地位:
分析源码之前对相关API
过下:
- 原本没有使用中间件时候写法
createStore
的写法:
const store createStore(reducer, initialState);
- 使用中间件
createStore
的写法
const store = createStore(reducer, applyMiddleware(thunk, logger));
- 对于
applyMiddleware
的api
其实也可以构造store
的,虽然不常这样用,这里先给出,后边源码分析会给出解释(其实就是做了一个参数判断)。
const store = applyMiddleware(...middlewares)(createStore)(reducer, initialState)
上述api使用中一些参数的解释:
- 其中
middlewares
是中间件组成的数组。而且applyMiddleware
接受的参数都是中间件,例如上边的redux-thunk
处理异步action
,logger
打印dispatch
前后的状态数据。 createStore
和applyMiddleware
都是import自Redux
的方法。文主要对这两个方法进行分析。reducer
纯函数作用是接受action
和旧的state
返回新的state
,initialState
初始的state。
createStore源码
preloadedState
对应上文提到的initalState
enhancer
对应上文提到的applyMiddleware(thunk, logger)
的运行结果(这个运行结果实际上是一个函数——compose
后边会详述)。
export default function createStore(reducer, preloadedState, enhancer) {
/*
对于 preloadedState 的判断
如果 preloadedState 和 enhancer 都是函数,
或者 enhancer 之后有多个函数,报错
*/
if (
(typeof preloadedState === 'function' && typeof enhancer === 'function') ||
(typeof enhancer === 'function' && typeof arguments[3] === 'function')
) {
throw new Error(
'It looks like you are passing several store enhancers to ' +
'createStore(). This is not supported. Instead, compose them ' +
'together to a single function.'
)
}
/*
如果 preloadedState 函数,而 enhancer没有传,
说明 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.')
}
// 返回使用过中间件后构造出的 store
return enhancer(createStore)(reducer, preloadedState)
}
//省略....初始化 store ,并对 store 做一些发布订阅
}
看到这里就明白了上边为什么说
对于
applyMiddleware
的api
其实也可以构造store
的。
其实createStore
就是做了一层判断,如果你们想要使用更高级的中间件
,我可以帮你搞定,并把自己creastore
给传进applyMiddleware
,这也说明最终的store
毕竟还是要靠这个createStore
来做(毕竟store
那么多的订阅逻辑,肯定不能重复再写一遍)。
applyMiddleware 解析
千呼万唤始出来,犹抱琵琶半遮面。
接下里到重头戏了。
首先明确,这个结果肯定是一个store
,这是没跑的(有中间件情况下的createStore
函数返回值嘛),
applyMiddleware(createStore)(reducer, preloadedState)
而且store
肯定包含 getState
、dispatch
的方法。
那么结合文章开头的那个图片,可以猜到,applyMiddleware
一定是这样的结构
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
/*
1.构建 store
2.增强 dispatch 的功能。
*/
return {
...store,
dispatch
}
}
}
- 我们心里大可去接受:中间件的作用就是增强
dispatch
的功能,那么接下来的基本思路肯定就是创建store
,然后覆写这个dispatch
。
在此之前先瞄一下中间件的结构:
export default ({getState, dispatch}) => next => action => {
/*在某个合适的时机或者地方*/
next(action);
}
可以发现:其实中间件就是一个回调函数,每次调用完毕会把自己的处理结果
给下一个中间件(回调)。
- 利用上边的
中间件(函数)
+原始dispatch(createStore返回的)
得到一个增强版的dispatch
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)
};
// 1、将store对象的基本方法传递给每个中间件并依次调用中间件
const chain =
middlewares.map(middleware => middleware(middlewareAPI));
// 2、改变dispatch指向,并将最初的dispatch传递给compose
dispatch = compose(...chain)(store.dispatch);
注释1:先定义了一个运行一定会报错的dispatch
,然后注入到每个中间件中。这样做是为了防止在中间件构建过程中调用dispatch
,不然,可以想象在一个中间件(函数)中直接调用原始dispatch
,那么其他的中间件会被跳过(类似于流水线上,中间一个机器不听话,直接将物品丢到了最后一个机器,那么这样做出来的物品肯定是不完美的)。
注意:此处每个中间件(函数)已经运行了一层,得到:
a = next => action => {
/*在某个地方*/
next(action);
}
注释2:改变dispatch
的指向。其中用到了compose
函数,compose
函数的功能就是集所有中间件功能于大成,将整个流水线拼装起来。(不得不吹, compose
函数写得真的赞,深深体味到了函数式编程的优美)。
function compose(...chain){
//边界条件
if (chain.length === 0) {
return arg => arg;
}
if(chain.length === 1){
return chain[0];
}
// 核心
return chain.reduce( (a,b) => (...args) => a(b(...args)) );
}
reduce 起到迭代的作用,详见
- 最终,我们得到了一整条流水线compose(…chain),然后通过
原始dispatch
去“启动”这条流水线。
具体分析一个例子:
const chain = [a, b, c];
compose(...chain)(store.dispatch);
//最终得到的结果:a(b(c(dispatch)))
上图做解释:
- 最最终用这个
a(b(c(dispatch)))
增强版的dispatch
代替store
下原本的dispatch
。
return {
...store,
dispatch
}
注意:此处的中间件(函数)已经又调用了一层,得到:
dispatch = action => {
/*在某个地方*/
next(action);
}
为了帮助理解,那如果我们在某个地方调用了这个增强版的dispatch
那么过程是什么样子呢?
上图:
以身试法
- 对一篇文章进行单词提取。以
, . ; : ' " ?
为界限进行分割。
/*
对一段引文文章,获取每个单词,
对 , . ; : ' " ? 进行分割
*/
function splitArticle(base) {
return function (content) {
let arr = content.split(base);
return arr.reduce((total, item) => total + item, '');
}
}
const g = function (content) {
const base = [',', '.', ';', ':', '\'', '"', '?'];
const chain = base.map( item => splitArticle(item));
let compose = chain.reduce((a, b) => (str) => {
return a(b(str));
});
return compose(content).split(" ");
}
let content = 'Among humans, smiling is an expression denoting pleasure, sociability, happiness, joy or amusement. It is distinct from a similar but usually involuntary expression of anxiety known as a grimace. Although cross-cultural studies have shown that smiling is a means of communication throughout the world,[1] there are large differences among different cultures, religions and societies, with some using smiles to convey confusion or embarrassment.';
let result = g(content);
console.log('result', result);
总结
对于compose 函数这样的写法,可以用在 对一个对象多次类似连续操作
的应用场景下。
- 连续。上一步处理的结果是我感兴趣的,而且我处理的结果同样会被下个处理感兴趣。这样类似
环环相扣
的抽象感觉。 - 类似。只有类似的处理逻辑,才能借助reduce组合成一个
流水线
。
但感觉连续的要求可能要更多一点
,因为毕竟再不相同的逻辑场景,我们可以去做一些改动让他们结构相似就可以了。