Redux Middleware运用
发布时间:2023-04-06 13:59:27 所属栏目:教程 来源:
导读:我们已经在异步 Action 一节的示例中看到了一些 middleware 的使用。如果你使用过 Express 或者 Koa 等服务端框架, 那么应该对 middleware 的概念不会陌生。 在这类框架中,middleware 是指可以被嵌入在框架接收请求
我们已经在异步 Action 一节的示例中看到了一些 middleware 的使用。如果你使用过 Express 或者 Koa 等服务端框架, 那么应该对 middleware 的概念不会陌生。 在这类框架中,middleware 是指可以被嵌入在框架接收请求到产生响应过程之中的代码。例如,Express 或者 Koa 的 middleware 可以完成添加 CORS headers、记录日志、内容压缩等工作。middleware 最优秀的特性就是可以被链式组合。你可以在一个项目中使用多个独立的第三方 middleware。 相对于 Express 或者 Koa 的 middleware,Redux middleware 被用于解决不同的问题,但其中的概念是类似的。它提供的是位于 action 被发起之后,到达 reducer 之前的扩展点。 你可以利用 Redux middleware 来进行日志记录、创建崩溃报告、调用异步接口或者路由等等。 这个章节分为两个部分,前面是帮助你理解相关概念的深度介绍,而后半部分则通过一些实例来体现 middleware 的强大能力。对文章前后内容进行结合通读,会帮助你更好的理解枯燥的概念,并从中获得启发。 理解 Middleware 正因为 middleware 可以完成包括异步 API 调用在内的各种事情,了解它的演化过程是一件相当重要的事。我们将以记录日志和创建崩溃报告为例,引导你体会从分析问题到通过构建 middleware 解决问题的思维过程。 问题: 记录日志 使用 Redux 的一个益处就是它让 state 的变化过程变的可预知和透明。每当一个 action 发起完成后,新的 state 就会被计算并保存下来。State 不能被自身修改,只能由特定的 action 引起变化。 试想一下,当我们的应用中每一个 action 被发起以及每次新的 state 被计算完成时都将它们记录下来,岂不是很好?当程序出现问题时,我们可以通过查阅日志找出是哪个 action 导致了 state 不正确。 Redux Middleware 我们如何通过 Redux 实现它呢? 尝试 #1: 手动记录 最直接的解决方案就是在每次调用 store.dispatch(action) 前后手动记录被发起的 action 和新的 state。这称不上一个真正的解决方案,仅仅是我们理解这个问题的第一步。 注意 如果你使用 react-redux 或者类似的绑定库,最好不要直接在你的组件中操作 store 的实例。在接下来的内容中,仅仅是假设你会通过 store 显式地向下传递。 假设,你在创建一个 Todo 时这样调用: store.dispatch(addTodo('Use Redux')) 为了记录这个 action 以及产生的新的 state,你可以通过这种方式记录日志: let action = addTodo('Use Redux') console.log('dispatching', action) store.dispatch(action) console.log('next state', store.getState()) 虽然这样做达到了想要的效果,但是你并不想每次都这么干。 尝试 #2: 封装 dispatch 你可以将上面的操作抽取成一个函数: function dispatchAndLog(store, action) { console.log('dispatching', action) store.dispatch(action) console.log('next state', store.getState()) } 然后用它替换 store.dispatch(): dispatchAndLog(store, addTodo('Use Redux')) 你可以选择到此为止,但是每次都要导入一个外部方法总归还是不太方便。 尝试 #3: Monkeypatching dispatch 如果我们直接替换 store 实例中的 dispatch 函数会怎么样呢?Redux store 只是一个包含一些方法的普通对象,同时我们使用的是 JavaScript,因此我们可以这样实现 dispatch 的 monkeypatch: let next = store.dispatch store.dispatch = function dispatchAndLog(action) { console.log('dispatching', action) let result = next(action) console.log('next state', store.getState()) return result } 这离我们想要的已经非常接近了!无论我们在哪里发起 action,保证都会被记录。Monkeypatching 令人感觉还是不太舒服,不过利用它我们做到了我们想要的。 问题: 崩溃报告 如果我们想对 dispatch 附加超过一个的变换,又会怎么样呢? 我脑海中出现的另一个常用的变换就是在生产过程中报告 JavaScript 的错误。全局的 window.onerror 并不可靠,因为它在一些旧的浏览器中无法提供错误堆栈,而这是排查错误所需的至关重要信息。 试想当发起一个 action 的结果是一个异常时,我们将包含调用堆栈,引起错误的 action 以及当前的 state 等错误信息通通发到类似于 Sentry 这样的报告服务中,不是很好吗?这样我们可以更容易地在开发环境中重现这个错误。 然而,将日志记录和崩溃报告分离是很重要的。理想情况下,我们希望他们是两个不同的模块,也可能在不同的包中。否则我们无法构建一个由这些工具组成的生态系统。(提示:我们正在慢慢了解 middleware 的本质到底是什么!) 如果按照我们的想法,日志记录和崩溃报告属于不同的模块,他们看起来应该像这样: function patchStoretoAddLogging(store) { let next = store.dispatch store.dispatch = function dispatchAndLog(action) { console.log('dispatching', action) let result = next(action) console.log('next state', store.getState()) return result } } function patchStoretoAddCrashReporting(store) { let next = store.dispatch store.dispatch = function dispatchAndReportErrors(action) { try { return next(action) } catch (err) { console.error('捕获一个异常!', err) Raven.captureException(err, { extra: { action, state: store.getState() } }) throw err } } } 如果这些功能以不同的模块发布,我们可以在 store 中像这样使用它们: patchStoretoAddLogging(store) patchStoretoAddCrashReporting(store) 尽管如此,这种方式看起来还是不是够令人满意。 尝试 #4: 隐藏 Monkeypatching Monkeypatching 本质上是一种 hack。“将任意的方法替换成你想要的”,此时的 API 会是什么样的呢?现在,让我们来看看这种替换的本质。 在之前,我们用自己的函数替换掉了 store.dispatch。如果我们不这样做,而是在函数中返回新的 dispatch 呢? function logger(store) { let next = store.dispatch // 我们之前的做法: // store.dispatch = function dispatchAndLog(action) { return function dispatchAndLog(action) { console.log('dispatching', action) let result = next(action) console.log('next state', store.getState()) return result } } 我们可以在 Redux 内部提供一个可以将实际的 monkeypatching 应用到 store.dispatch 中的辅助方法: function applyMiddlewareByMonkeypatching(store, middlewares) { middlewares = middlewares.slice() middlewares.reverse() // 在每一个 middleware 中变换 dispatch 方法。 middlewares.forEach(middleware => store.dispatch = middleware(store) ) } 然后像这样应用多个 middleware: applyMiddlewareByMonkeypatching(store, [ logger, crashReporter ]) 尽管我们做了很多,实现方式依旧是 monkeypatching。 因为我们仅仅是将它隐藏在我们的框架内部,并没有改变这个事实。 尝试 #5: 移除 Monkeypatching 为什么我们要替换原来的 dispatch 呢?当然,这样我们就可以在后面直接调用它,但是还有另一个原因:就是每一个 middleware 都可以操作(或者直接调用)前一个 middleware 包装过的 store.dispatch: function logger(store) { // 这里的 next 必须指向前一个 middleware 返回的函数: let next = store.dispatch return function dispatchAndLog(action) { console.log('dispatching', action) let result = next(action) console.log('next state', store.getState()) return result } } 将 middleware 串连起来的必要性是显而易见的。 如果 applyMiddlewareByMonkeypatching 方法中没有在第一个 middleware 执行时立即替换掉 store.dispatch,那么 store.dispatch 将会一直指向原始的 dispatch 方法。也就是说,第二个 middleware 依旧会作用在原始的 dispatch 方法。 但是,还有另一种方式来实现这种链式调用的效果。可以让 middleware 以方法参数的形式接收一个 next() 方法,而不是通过 store 的实例去获取。 (编辑:汽车网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
推荐文章
站长推荐