最近在读Redux源码,现将自己实现的代码和理解贴上,后期再补充详细~
中间件:类似于插件,可以在不影响原本功能、并且不改动原本代码的基础上,对其功能进行增强。在Redux中,中间件主要用于增强dispatch函数。
实现Redux中间件的基本原理,是更改仓库中的dispatch函数。
中间件本身是一个函数,该函数接收一个store参数,表示创建的仓库,该仓库并非一个完整的仓库对象,仅包含getState,dispatch。该函数运行的时间,是在仓库创建之后运行。
由于创建仓库后需要自动运行设置的中间件函数,因此,需要在创建仓库时,告诉仓库有哪些中间件。需要调用applyMiddleware函数,将函数的返回结果作为createStore的第二或第三个参数。
中间件函数必须返回一个dispatch创建函数,applyMiddleware函数,用于记录有哪些中间件,它会返回一个函数,该函数用于记录创建仓库的方法,然后又返回一个函数。
手写applyMiddleware
middleware的本质,是一个调用后可以得到dispatch创建函数的函数。
compose:函数组合,将一个数组中的函数进行组合,形成一个新的函数,该函数调用时,实际上是反向调用之前组合的函数。
主代码:
import {compose} from "./compose";
/**
* 注册中间件
* @param {...any} middlewares
*/
export function applyMiddleware(...middlewares) {
// 接收creatStore方法
return function (createStore) {
// 接收reducer和默认状态 ,用于创建仓库
return function (reducer, defaultState) {
//创建仓库
const store = createStore(reducer, defaultState);
let dispatch = () => { throw new Error("目前还不能使用dispatch") };
const simpleStore = {
getState: store.getState,
// 这里不能写成dispatch: dispatch,否则一直是上面那个报错的dispatch
// 也不能写成store.dispatch,否则一直是最原始的dispatch
// 写成函数形式是为了保证引用地址一致
dispatch: (...args) => dispatch(...args)
}
//给dispatch赋值
//根据中间件数组,得到一个dispatch创建函数的数组
const dispatchProducers = middlewares.map(mid => mid(simpleStore));
// 在完成dispatch前,是调用不了simpleStore中的dispatch的,只有等包装完
dispatch = compose(...dispatchProducers)(store.dispatch)
return {
...store,
dispatch,
}
}
}
}
compose代码:
export function compose(...funcs) {
if (funcs.length === 0) {
return args => args; //如果没有要组合的函数,则返回的函数原封不动的返回参数
} else if (funcs.length === 1) {
//要组合的函数只有一个
return funcs[0];
}
// redux官方写法,在下面附有步骤分析,非常巧妙
// return funcs.reduce((a, b) => (...args) => a(b(...args)));
// 这是可读性好一些的写法,和上面代码功能一样
return function (...args) {
let lastReturn = null; // 记录上一个函数返回的值
for (let i = funcs.length - 1; i >= 0; i--) {
const func = funcs[i];
if (i === funcs.length - 1) {
lastReturn = func(...args);
} else {
lastReturn = func(lastReturn);
}
}
return lastReturn;
}
}
function func1(n) {
return n * 2;
}
function func2(n) {
return n + n
}
function func3(n) {
return n - 5
}
[func1, func2, func3].reduce((a,b)=>(...args) => a(b(...args)));
/**
* (...args) => func1(func2(...args)), func3
* (...args) => ((...args) => func1(func2(...args)))(func3(...args))
* 函数执行变成如下代码
* (...args) => func1(func2(func3(...args)))
*/
上面的实例代码传入一个数,会发现是从func3至func1运行的,参照redux中间件写法才是从func1至func3运行,代码如下所示:
function func1 (next) {
return function (action) {
action = action * 2
next(action);
}
}
function func2 (next) {
return function (action) {
action = action + 20
next(action);
}
}
function func3 (next) {
return function (action) {
action = action / 4
next(action);
}
}
let comp = [func1, func2, func3].reduce((a,b)=>(...args) => a(b(...args)))((action)=>console.log(action))
comp(6)
// 输出 8
上述代码是从func3开始,至func1将代码一层层包裹起来,这也就是洋葱模型,当运行时就会从func1开始运行了。