8.说说Redux的中间件(Middleware)的工作机制 副本 来源:https://my.feishu.cn/docx/Ot9Kd35XroMXsyxWiWycEBoXnUb 采集状态:已完成正文采集;已滚动到底部并按块及代码行号采集 在 Redux 的生态中,Reducer 必须是纯函数,这意味着它只负责根据旧的 State 和 Action 来计算新的 State,不能包含任何副作用,例如网络请求、定时器、或者访问浏览器 API。但在实际的 Web 应用开发中,处理异步操作是不可避免的。比如,用户点击按钮后,我们需要向服务器发起请求,然后根据请求成功或失败的结果来更新状态。​ ​ 我们常常遇到这样一个场景:一个 Action 被 dispatch 之后,我们希望在它到达 Reducer 之前执行一些额外的逻辑,尤其是异步逻辑。如果直接将这些副作用放在 Reducer 中,就破坏了 Redux 的核心原则;如果将它们分散在各个组件中,又会导致逻辑混乱和难以维护。​ ​ 为了优雅地解决这个矛盾,Redux 引入了中间件(Middleware)机制。它为我们提供了一个统一、可组合的方式来处理这些副作用,同时保持 Reducer 的纯粹性。​ ​ 什么是中间件?​ ​ 我们可以把 Redux 中间件想象成一个位于 Action 发出后、到达 Reducer 前的“关卡”或“扩展点”。当一个 Action 被 dispatch 时,它不会立即到达 Reducer,而是会先穿过一个由所有中间件组成的链条。​ ​ 我们可以用一个简单的流程图来表示这个过程:​ ​ Action -> Middleware 1 -> Middleware 2 -> ... -> Reducer​ ​ 在这个链条上,每个中间件都可以接触到被派发的 Action,并能访问到 Store 的 getState() 和 dispatch() 方法。这赋予了中间件强大的能力:​ ​ • 执行副作用:比如发起 API 请求。​ • 修改或延迟 Action:可以等待异步操作完成后,再派发一个新的 Action。​ • 中止 Action:在特定条件下,阻止某个 Action 到达 Reducer。​ • 派发新的 Action:在处理一个 Action 的过程中,可以派发其他完全不同的 Action。 本质上,中间件通过增强(monkey-patching)Redux Store 的 dispatch 方法,为我们提供了一个介入数据流处理过程的机会。​ ​ 中间件的核心签名:store => next => action​ ​ 所有 Redux 中间件都遵循一个看似有些复杂的函数签名。它是一个三层嵌套的柯里化函数:​ ​ const myMiddleware = store => next => action => { // 中间件的逻辑在这里 } ​ 为了更好地理解它,我们把它拆解来看:​ ​ 1. store:这是函数的第一层接收的参数。Redux 在应用中间件时,会将 Store 实例传递进来。因此,在中间件内部,我们可以通过 store.getState() 获取当前状态,或通过 store.dispatch() 派发新的 Action。 2. next:这是第二层函数接收的参数。next 是一个函数,它是中间件链条中的“接力棒”。当我们调用 next(action) 时,就是将这个 Action 传递给链条中的**下一个**中间件。如果当前中间件是最后一个,那么 next 就是原始的 store.dispatch 方法,调用它会将 Action 直接发送给 Reducer。**如果我们不调用 next(action),那么这个 Action 将被“拦截”,无法继续传递下去。 3. action:这是最内层函数接收的参数,也就是当前正在被处理的 Action 对象。 这个精巧的结构使得中间件可以形成一个清晰的洋葱模型。每个中间件都可以决定在调用 next(action) **之前**和**之后**做什么。​ ​ 实践:编写一个简单的日志中间件​ ​ 理论不如实践,我们来编写一个最经典的日志中间件 logger。它的功能是在每个 Action 到达 Reducer 前后,分别打印出 Action 的内容、变化前的状态和变化后的状态。​ ​ const loggerMiddleware = store => next => action => { // 1. Action 到达 Reducer 之前的逻辑 console.log('Dispatching:', action); console.log('State before:', store.getState()); // 2. 调用 next,将 Action 传递给下一个中间件或 Reducer // 这是整个流程中的“分割点” const result = next(action); // 3. Action 处理完毕后的逻辑 console.log('State after:', store.getState()); // 4. 返回 next(action) 的结果 return result; }; ​ 这个例子清晰地展示了中间件的工作模式:​ ​ • 代码在 next(action) 之前执行,这是 Action 到达 Reducer 之前的阶段。​ • next(action) 是一个关键的调用,它触发了后续的中间件以及最终的 Reducer。​ • 代码在 next(action) 之后执行,这时 Reducer 已经完成了状态更新,我们可以获取到最新的 State。 处理异步请求:中间件的真正威力​ ​ 现在,我们来解决文章开头提到的异步请求问题。最常见的方式是使用像 redux-thunk 这样的中间件。Thunk 的核心思想很简单:让 Action Creator 不仅能返回一个 Action 对象,还能返回一个函数。​ ​ 我们可以自己实现一个简化的 thunk 中间件来理解其原理:​ ​ const thunkMiddleware = store => next => action => { // 如果 action 不是一个函数,那么这个中间件什么也不做, // 直接把它传递给下一个中间件或 Reducer。 if (typeof action !== 'function') { return next(action); } // 如果 action 是一个函数,我们就执行它, // 并将 store 的 dispatch 和 getState 方法作为参数传进去。 // 这样,在异步逻辑内部,我们就可以自由地派发新的 Action 或获取当前状态。 return action(store.dispatch, store.getState); }; ​ 有了这个中间件,我们就可以这样编写异步 Action 了:​ ​ // 这是一个 Action Creator,但它返回了一个函数,而不是 Action 对象 const fetchUserData = () => { // 这个函数会被 thunkMiddleware 执行 return async (dispatch, getState) => { // 派发一个 "请求开始" 的 Action dispatch({ type: 'FETCH_USER_REQUEST' }); try { // 执行异步操作 const response = await fetch('/api/user'); const user = await response.json(); // 异步成功后,派发 "请求成功" 的 Action,并附带数据 dispatch({ type: 'FETCH_USER_SUCCESS', payload: user }); } catch (error) { // 异步失败后,派发 "请求失败" 的 Action,并附带错误信息 dispatch({ type: 'FETCH_USER_FAILURE', payload: error.message }); } }; }; // 在组件中这样使用: // store.dispatch(fetchUserData()); ​ 通过这种方式,thunkMiddleware 拦截了函数形式的 action,并执行它,从而将异步流程控制权交给了我们。而同步的 Action 对象则被忽略,继续沿着中间件链条传递,最终由 Reducer 处理。整个过程保持了 Reducer 的纯粹性。​ ​ 这一切是如何组合起来的:applyMiddleware​ ​ 我们创建好了中间件,那么 Redux 是如何加载并使用它们的呢?答案是 createStore 函数的第二个(或第三个)参数 applyMiddleware。​ ​ applyMiddleware 是 Redux 提供的一个高阶函数,它接收任意数量的中间件作为参数,并返回一个 Store Enhancer (一个增强器函数)。​ ​ import { createStore, applyMiddleware } from 'redux'; import rootReducer from './reducers'; import loggerMiddleware from './middlewares/logger'; import thunkMiddleware from './middlewares/thunk'; // 使用 applyMiddleware 将所有中间件组合起来 const store = createStore( rootReducer, applyMiddleware(loggerMiddleware, thunkMiddleware) ); ​ applyMiddleware 的内部机制大致是这样的:​ ​ 1. 它获取 store.dispatch 的原始版本。​ 2. 将中间件数组通过函数式编程中的 compose 方法,从右到左组合成一个调用链。例如 applyMiddleware(m1, m2, m3) 会变成 m1(m2(m3(...))) 的结构。​ 3p. 它将 store.dispatch 作为这个组合链条的最终 next 函数,然后生成一个全新的、被所有中间件包裹起来的“增强版” dispatch 函数。​ 4. 最后,用这个新的 dispatch 替换掉原始的 store.dispatch。 之后,每当我们调用 store.dispatch(action) 时,实际上触发的是经过层层包裹的中间件链条。​ ​ 总结​ ​ Redux 中间件是一个强大而灵活的机制,它优雅地解决了在函数式数据流中处理副作用的难题。我们可以总结出它的几个核心特点:​ ​ • 核心目的:处理副作用(如异步请求),保持 Reducer 的纯粹性。​ • 工作位置:位于 dispatch 和 Reducer 之间,形成一个可插拔的处理链。​ • 核心签名:store => next => action 的柯里化结构,使其易于组合和链式调用。​ • 关键函数:next(action) 是驱动 Action 在中间件链条中传递的“引擎”。​ • 集成方式:通过 applyMiddleware 函数应用到 Store 中,它会生成一个被增强的 dispatch 方法。 它成功地将应用的业务逻辑(如数据获取、缓存、日志等)从视图层和纯粹的状态管理中剥离出来,让我们的代码结构更加清晰、可预测和易于维护。希望通过这篇文章,我们能够对 Redux 中间件的机制有一个更清晰、更深入的理解。​ ​ 本文暂未被其它文档引用