您的位置:首页 > 其它

理解 Redux 的中间件

2019-10-08 23:54 841 查看

将该思想抽象出来,其实和 Redux 就无关了。问题变成,怎样实现在截获函数的执行,以在其执行前后添加自己的逻辑。

为了演示,我们准备如下的示例代码来模拟 Redux dispatch action 的场景:

const store = {
dispatch: action => {
console.log("dispating action:", action);
}
};

store.dispatch({ type: "FOO" });
store.dispatch({ type: "BAR" });

我们最终需要实现的效果是 Redux 中

applyMiddleware(...middlewares)
的效果,接收一个中间件数据(函数数组),执行真正的 dispatch 前顺次执行这些中间件。

以打日志为例,我们想在调用 dispatch 时进行日志输出。

尝试1 - 手动

直接的做法就是手动进行。

console.log("before dispatch `FOO`");
store.dispatch({ type: "FOO" });
console.log("before dispatch `FOO`");

console.log("before dispatch `BAR`");
store.dispatch({ type: "BAR" });
console.log("before dispatch `BAR`");

但其实这并不算一个系统的解决方案,至少需要摆脱手动这种方式。

尝试2 - 包装

既然所有 dispatch 操作都会打日志,完全有理由抽取一个方法,将 dispatch 进行包装,在这个方法里来做这些事情。

function dispatchWithLog(action) {
console.log(`before dispatch ${action.type}`);
store.dispatch(action);
console.log(`after dispatch ${action.type}`);
}

但调用的地方也得变,不能直接使用原始的

store.disatch
而需要使用封装后的
dispatchWithLog

- store.dispatch({ type: "FOO" });
- store.dispatch({ type: "BAR" });
+ dispatchWithLog({ type: "FOO" });
+ dispatchWithLog({ type: "BAR" });

尝试3 - 替换实现/Monkeypatching

如果我们直接替换掉原始函数的实现,便可以做到调用的地方不受影响而实现新增的 log 功能,虽然修改别人提供的方法容易引起 bug 且不太科学。

const original = store.dispatch;
store.dispatch = function log(action) {
console.log(`before dispatch ${action.type}`);
original(action);
console.log(`after dispatch ${action.type}`);
};

store.dispatch({ type: "FOO" });
store.dispatch({ type: "BAR" });

尝试4 - 多个函数的截获

除了添加 log,如果还想对每次 dispatch 进行错误监控,只需要拿到前面已经替换过实现的 dispatch 方法再次进行替换包装即可。

const original = store.dispatch;
store.dispatch = function log(action) {
console.log(`before dispatch ${action.type}`);
original(action);
console.log(`after dispatch ${action.type}`);
};

const next = store.dispatch;
store.dispatch = function report(action) {
console.log("report middleware");
try {
next(action);
} catch (error) {
console.log(`error while dispatching ${action.type}`);
}
};

所以针对单个功能的中间件,我们可以提取出其大概的样子来了:

function middleware(store) {
const next = store.dispatch;
store.dispatch = function(action) {
// 中间件中其他逻辑
next(action);
// 中间件中其他逻辑
};
}

改写日志和错误监控为如下:

function log(store) {
const next = store.dispatch;
store.dispatch = function(action) {
console.log(`before dispatch ${action.type}`);
next(action);
console.log(`after dispatch ${action.type}`);
};
}

function report(store) {
const next = store.dispatch;
store.dispatch = function(action) {
console.log("report middleware");
try {
next(action);
} catch (error) {
console.log(`error while dispatching ${action.type}`);
}
};
}

然后按需要应用上述中间件即可:

log(store);
report(store);

上面中间件的调用可专门编写一个方法来做:

function applyMiddlewares(store, middlewares) {
middlewares.forEach(middleware => middleware(store));
}

隐藏 Monkeypatching

真实场景下,各中间件由三方编写,如果每个中间件都直接去篡改

store.dispatch
不太科学也不安全。如此的话,中间件只需要关注新添加的逻辑,将新的 dispatch 返回即可,由框架层面拿到这些中间件后逐个调用并重写原来的
dispatch
,将篡改的操作收敛。

所以中间件的模式更新成如下:

function middleware(store) {
const next = store.dispatch;
-  store.dispatch = function(action) {
+  return function(action) {
// 中间件中其他逻辑
next(action);
// 中间件中其他逻辑
};
}

改写

log
report
中间件:

function log(store) {
const next = store.dispatch;
-  store.dispatch = function(action) {
+  return function(action) {
console.log(`before dispatch ${action.type}`);
next(action);
console.log(`after dispatch ${action.type}`);
};
}

function report(store) {
const next = store.dispatch;
-  store.dispatch = function(action) {
+  return function(action) {
console.log("report middleware");
try {
next(action);
} catch (error) {
console.log(`error while dispatching ${action.type}`);
}
};
}

更新

applyMiddlewares
方法:

function applyMiddlewares(store, middlewares) {
middlewares.forEach(middleware => {
store.dispatch = middleware(store);
});
}

最后,应用中间件:

applyMiddlewares(store, [log, report]);

进一步优化

之所以在应用中间件过程中每次都重新给

store.dispatch
赋值,是想让后续中间件在通过
store.dispatch
访问时,能够拿到前面中间件修改过的
dispatch
函数。

如果中间件中不是直接从

store
身上去获取
store.dispatch
,而是前面已经执行过的中间件将新的
dispatch
传递给中间件,则可以避免每次对
store.dispatch
的赋值。

function applyMiddlewares(store, middlewares) {
store.dispatch = middlewares.reduce(
(next, middleware) => middleware(next),
store.dispatch
);
}

忽略掉实际源码中的一些差异,以上,大致就是 Redux 中间件的创建和应用了。

测试

function m1(next) {
return function(action) {
console.log(`1 start`);
next(action);
console.log(`1 end`);
};
}
function m2(next) {
return function(action) {
console.log(`2 start`);
next(action);
console.log(`2 end`);
};
}
function m3(next) {
return function(action) {
console.log(`3 start`);
next(action);
console.log(`3 end`);
};

applyMiddlewares(store, [m1, m2, m3]);
store.dispatch({ type: "FOO" });
store.dispatch({ type: "BAR" });
}

输出结果:

3 start
2 start
1 start
dispating action: { type: 'FOO' }
1 end
2 end
3 end
3 start
2 start
1 start
dispating action: { type: 'BAR' }
1 end
2 end
3 end

相关资源

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: