我的观点是,看别人的源码,不追求一定要能原样造轮子,单纯就是学习知识,对于程序员的提高就足够了。在阅读redux的compose源码之前,我们先学一些前置的知识。
redux源码阅读之compose,applyMiddleware
看别人的源码就是学习知识。我们先学一些东西。
rest参数
形式为...变量名,用于获取函数的多余参数 ,该变量将多余的参数放入数组中, 只能是参数的最后一个。
function rest(...args){
console.log(args);
};
rest('arg1','arg2')
那么args变量就是一个数组['arg1','arg2']。
扩展运算符
扩展运算符也是三个点。好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列
arr.reduce(callback[initialValue])
个人感觉reduce用法非常魔幻,要搞明白它之后,才能明白compose。
MDN的reduce文档:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
使用
const array1 = [1, 2, 3, 4];
const reducer = (accumulator, currentValue) => accumulator + currentValue;
// 1 + 2 + 3 + 4
console.log(array1.reduce(reducer));
// 10
// 5 + 1 + 2 + 3 + 4
console.log(array1.reduce(reducer, 5));
// 15
array1.reduce((accumulator, currentValue) => accumulator + currentValue,5)
根据MDN的定义,
数组的reduce() 方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。
arr.reduce(callback[initialValue])
实际就是arr.reduce(callback, initialValue);
callback 函数接收4个参数:
Accumulator (acc) (累加器)
Current Value (cur) (当前值)
Current Index (idx) (当前索引)
Source Array (src) (源数组)
initialValue 可选
作为第一次调用 callback函数时的第一个参数的值。 如果没有提供初始值,则将使用数组中的第一个元素。 不传这个初始值initialVale,且在空数组上调用 reduce 将报 Uncaught TypeError: Reduce of empty array with no initial value的错误。
pip
研究compose之前,我们再来看MDN上reduce的应用,一个功能型函数管道。https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce#功能型函数管道
类似的我们来写一个。
const pipe = (...fns) =>
(args) => fns.reduce(
(args, fn) => fn(args)
,args)
吐槽一下,就是这种es6写法,完全不写return,很多es5过来的人不习惯,所以很多源码很难看清楚。我们改写一下,传入参数字符串‘test’,然后执行这个函数。就能明白pip函数的作用就是顺序执行了addFont,toUpper。
args就是'test',...fns就是rest参数,pip函数里的fns参数就是数组[addfont,toUpper]。
const pipe = (...fns) => {
return (args) => {
return fns.reduce((args, fn) => {
return fn(args)
}, args)
}
}
const toUpper = (value) => {
return value.toUpperCase();
}
const addFont = (value) => {
return 'hello plus!' + value;
}
console.log(pipe(addFont,toUpper)('test'));
// HELLO PLUS!TEST
pipe(addFont,toUpper)
之后,返回一个函数.
pipe(addFont,toUpper)('test')
形如
(args) => {
return fns.reduce((args, fn) => {
return fn(args)
}, args)
}('test');
再执行就是
fns.reduce(('test',fn)=>{
return fn('test');
},'test')
这样子看代码,这里的reduce应该似乎比较好懂的。
compose
compose源码
/**
* 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))).
*/
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)))
}
跟pip函数对比
console.log(pipe(addFont,toUpper)('test'));
// HELLO PLUS!TEST
console.log(compose(addFont,toUpper)('test'));
// hello plus!TEST
compose正好相反,后添加的方法先执行。然后写的也很玄学让人费解。让我们改成pip函数一样,也是同样的效果,感觉这样子就跟上面的pip一样好理解一些了呢。
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return (...funcs) => (...args) => funcs.reduce((a, b) => a(b(...args)))
}
console.log(compose(addFont,toUpper)('test'));
// hello plus!TEST
applyMiddleware
我们再来看redux引入中间件的用法,
const store = createStore(
reducer,
applyMiddleware(...middleware)
);
以及applyMiddleware方法源码
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)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
实际上所谓redux的中间件就是改写了store.dipatch方法。比如我们自己实现一个实现一个打印action的type的redux中间件,我们就可以这样写。
./redux-middlewares/log.js
const logger = ()=>{
return ({dispatch,getState}) => next => action =>{
console.log('dispatching', action);
return next(action);
}
};
export default logger;
跟别的redux中间件一起引入
import createSaga from 'redux-saga';
import createLog from './redux-middlewares/log';
const saga = createSaga();
const log = createLog();
const middlewares = [saga,log]
const store = createStore(
rootReducer,
applyMiddleware(...middlewares)
);
store.dispatch({type:'myAction'});
// 打印 dispatching {type: "myAction"}
这里
redux的添加中间件
applyMiddleware(...middleware)
就等于这里的
dispatch = compose(...chain)(store.dispatch)
.
跟我们实行
compose(addFont,toUpper)('test')
是一样的效果。
参考文章: