中间件
Redux 中间件详解

譬如我们应用中常用的日志功能,需要记录所有的
const logger = (store) => (next) => (action) => {
console.log("dispatching", action);
let result = next(action);
console.log("next state", store.getState());
return result;
};
然后在创建createStore
的第二个参数中:
import { createStore, combineReducers, applyMiddleware } from "redux";
let todoApp = combineReducers(reducers);
let store = createStore(
todoApp,
// applyMiddleware() tells createStore() how to handle middleware
applyMiddleware(logger)
);
这样中间件就能正常工作了,其分别在分发
调用流程
function middleware1(store) {
return function (next) {
return function (action) {
console.log("A middleware1 开始");
next(action);
console.log("B middleware1 结束");
};
};
}
function middleware2(store) {
return function (next) {
return function (action) {
console.log("C middleware2 开始");
next(action);
console.log("D middleware2 结束");
};
};
}
function middleware3(store) {
return function (next) {
return function (action) {
console.log("E middleware3 开始");
next(action);
console.log("F middleware3 结束");
};
};
}
function reducer(state, action) {
if (action.type === "MIDDLEWARE_TEST") {
console.log("======= G =======");
}
return {};
}
const store = Redux.createStore(
reducer,
Redux.applyMiddleware(middleware1, middleware2, middleware3)
);
store.dispatch({ type: "MIDDLEWARE_TEST" });
最后的控制台输出为:
A middleware1 开始
C middleware2 开始
E middleware3 开始
======= G =======
F middleware3 结束
D middleware2 结束
B middleware1 结束
整个请求的示意图如下:
--------------------------------------
| middleware1 |
| ---------------------------- |
| | middleware2 | |
| | ------------------- | |
| | | middleware3 | | |
| | | | | |
next next next ——————————— | | |
dispatch —————————————> | reducer | — 收尾工作->|
nextState <————————————— | G | | | |
| A | C | E ——————————— F | D | B |
| | | | | |
| | ------------------- | |
| ---------------------------- |
--------------------------------------
顺序 A -> C -> E -> G -> F -> D -> B
\---------------/ \----------/
↓ ↓
更新 state 完毕 收尾工作
Log Middleware
import { applyMiddleware, createStore } from "redux";
// Logger with default options
import logger from "redux-logger";
const store = createStore(reducer, applyMiddleware(logger));
// Note passing middleware as the third argument requires redux@>=3.1.0

或者使用自定义的配置:
import { applyMiddleware, createStore } from "redux";
import { createLogger } from "redux-logger";
const logger = createLogger({
// ...options
});
const store = createStore(reducer, applyMiddleware(logger));
源代码
export default function applyMiddleware<Ext, S = any>(
...middlewares: Middleware<any, S, any>[]
): StoreEnhancer<{ dispatch: Ext }>;
export default function applyMiddleware(
...middlewares: Middleware[]
): StoreEnhancer<any> {
return (createStore: StoreCreator) => <S, A extends AnyAction>(
reducer: Reducer<S, A>,
...args: any[]
) => {
const store = createStore(reducer, ...args);
let dispatch: Dispatch = () => {
throw new Error(
"Dispatching while constructing your middleware is not allowed. " +
"Other middleware would not be applied to this dispatch."
);
};
const middlewareAPI: MiddlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args),
};
const chain = middlewares.map((middleware) => middleware(middlewareAPI));
// chain 中的函数,(next) => (action) => {}
// compose 的结果就是 m1(m2(dispatch)),dispatch 作为最后一个中间件的 next 函数
// m1 的返回结果就是 (action)=>void
dispatch = compose<typeof dispatch>(...chain)(store.dispatch);
return {
...store,
dispatch,
};
};
}
这里的
// export default function compose<R>(...funcs: Function[]): (...args: any[]) => R
export default function compose(...funcs: Function[]) {
if (funcs.length === 0) {
// infer the argument type so it is usable in inference down the line
return <T>(arg: T) => arg;
}
if (funcs.length === 1) {
return funcs[0];
}
// 这里的 b(...args) 相当于 next
return funcs.reduce((a, b) => (...args: any) => a(b(...args)));
}
Redux 中的定时器
组件内定时器
没有什么可以阻止您在组件内部使用计时器的。您可以在
export default class Loading extends Component {
state = {
timer: null,
counter: 0
};
componentDidMount() {
let timer = setInterval(this.tick, 1000);
this.setState({timer});
}
componentWillUnmount() {
this.clearInterval(this.state.timer);
}
tick() {
this.setState({
counter: this.state.counter + 1
});
}
render() {
<div>Loading{"...".substr(0, this.state.counter % 3 + 1)}</div>
}
这种方式优势在于简单直接,组件可以是独立的。缺陷在于状态是内部的,因此很难与其他组件共享、状态是内部的,这使其更难以存储和调和、特定于
Timers in Actions
另一种选择是在调度动作时触发计时器。使用
let timer = null;
const start = () => (dispatch) => {
clearInterval(timer);
timer = setInterval(() => dispatch(tick()), 1000);
dispatch({ type: TIMER_START });
dispatch(tick());
};
const tick = () => {
type: TIMER_TICK;
};
const stop = () => {
clearInterval(timer);
return { type: TIMER_STOP };
};
这种方式的优势在于您可以跟踪与计时器相关的动作和状态突变、您可以在整个应用程序中共享计时器状态。缺陷在于关闭
Global timer
当您的应用程序加载“监视”需要触发进一步操作的应用程序状态时,可以在代码中的某个位置启动全局计时器。例如:
//somewhere when you app starts
setInterval(() => {
let actions = calculate_pending_actions(store.getState());
actions.forEach(dispatch);
}, 50); //something short
function calculate_pending_actions(state) {
let { timer } = state;
let actions = [];
// put all your conditions here...
if (timer.started && new Date().getTime() - timer.startedOn > 60 * 1000) {
actions.push(stop());
}
// etc...
return actions;
}
到目前为止,最后一个选项是最复杂的,并且与游戏的构建方式有些相似。这种方式优点在于无需处理启动
缺点在于这是最复杂的方法、以特定的时间间隔调度操作可能会比较棘手。