• 理解 Redux 的中间件


    将该思想抽象出来,其实和 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(</span>error while dispatching <span class="pl-s1"><span class="pl-pse">${</span><span class="pl-smi">action</span>.<span class="pl-c1">type</span><span class="pl-pse">}</span></span><span class="pl-pds">);
    }
    };

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

    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(</span>error while dispatching <span class="pl-s1"><span class="pl-pse">${</span><span class="pl-smi">action</span>.<span class="pl-c1">type</span><span class="pl-pse">}</span></span><span class="pl-pds">);
    }
    };
    }

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

    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);
        // 中间件中其他逻辑
      };
    }

    改写 logreport 中间件:

    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

    相关资源

  • 相关阅读:
    分享两个你可能不知道的Java小秘密
    一次ssl的手动实现——加密算法的简单扫荡
    TCP/IP中最高大上的链路层简介(二)
    与TCP/IP协议的初次见面(一)
    高并发下的九死一生,一个不小心就掉入万丈深渊
    杂谈---一个项目经理的自我反省
    浅谈程序员的行业选择---程序人生
    杂谈---大压力下的工作
    一个有意思的需求——中文匹配度
    杂谈---一个人的两种心理
  • 原文地址:https://www.cnblogs.com/Wayou/p/understanding_redux_middleware.html
Copyright © 2020-2023  润新知