实现Redux中间件机制
手写实现Redux的applyMiddleware函数,理解中间件的洋葱模型和函数组合原理
问题
Redux中间件是Redux架构中的重要组成部分,它允许我们在action被dispatch到reducer之前进行拦截和处理。需要实现一个applyMiddleware函数,支持:
- 接收多个中间件函数
- 按照洋葱模型执行中间件
- 增强store的dispatch方法
- 支持中间件访问store的getState和dispatch
解答
/**
* 实现Redux的applyMiddleware函数
* @param {...Function} middlewares - 中间件函数数组
* @returns {Function} - 返回一个store增强器
*/
function applyMiddleware(...middlewares) {
// 返回一个store增强器函数
return (createStore) => (reducer, preloadedState) => {
// 创建原始store
const store = createStore(reducer, preloadedState);
// 保存原始dispatch,防止在中间件构造过程中被调用
let dispatch = () => {
throw new Error('不能在构造中间件时dispatch');
};
// 提供给中间件的API
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action) // 使用闭包引用最新的dispatch
};
// 将middlewareAPI注入到每个中间件中,得到中间件链
const chain = middlewares.map(middleware => middleware(middlewareAPI));
// 使用compose组合所有中间件,增强原始dispatch
dispatch = compose(...chain)(store.dispatch);
// 返回增强后的store
return {
...store,
dispatch
};
};
}
/**
* 函数组合工具 - 从右到左组合函数
* compose(f, g, h) 等价于 (...args) => f(g(h(...args)))
*/
function compose(...funcs) {
if (funcs.length === 0) {
return (arg) => arg;
}
if (funcs.length === 1) {
return funcs[0];
}
// 使用reduce从右到左组合函数
return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
/**
* 简化版的createStore实现(用于测试)
*/
function createStore(reducer, preloadedState) {
let state = preloadedState;
let listeners = [];
const getState = () => state;
const dispatch = (action) => {
state = reducer(state, action);
listeners.forEach(listener => listener());
return action;
};
const subscribe = (listener) => {
listeners.push(listener);
return () => {
listeners = listeners.filter(l => l !== listener);
};
};
// 初始化state
dispatch({ type: '@@INIT' });
return { getState, dispatch, subscribe };
}
使用示例
// 示例1:日志中间件
const loggerMiddleware = (store) => (next) => (action) => {
console.log('dispatching:', action);
console.log('prev state:', store.getState());
const result = next(action);
console.log('next state:', store.getState());
return result;
};
// 示例2:异步中间件(类似redux-thunk)
const thunkMiddleware = (store) => (next) => (action) => {
// 如果action是函数,则执行它,并传入dispatch和getState
if (typeof action === 'function') {
return action(store.dispatch, store.getState);
}
// 否则正常dispatch
return next(action);
};
// 示例3:错误处理中间件
const errorMiddleware = (store) => (next) => (action) => {
try {
return next(action);
} catch (error) {
console.error('捕获到错误:', error);
// 可以dispatch一个错误action
store.dispatch({ type: 'ERROR_OCCURRED', error });
}
};
// 定义reducer
function counterReducer(state = { count: 0 }, action) {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
case 'ADD':
return { count: state.count + action.payload };
default:
return state;
}
}
// 创建增强后的store
const store = applyMiddleware(
loggerMiddleware,
thunkMiddleware,
errorMiddleware
)(createStore)(counterReducer);
// 使用示例
console.log('=== 同步action ===');
store.dispatch({ type: 'INCREMENT' });
console.log('\n=== 异步action(thunk)===');
store.dispatch((dispatch, getState) => {
console.log('异步操作开始');
setTimeout(() => {
dispatch({ type: 'ADD', payload: 5 });
console.log('异步操作完成,当前state:', getState());
}, 1000);
});
// 订阅state变化
store.subscribe(() => {
console.log('State changed:', store.getState());
});
关键点
-
柯里化设计:中间件采用三层柯里化结构
store => next => action,便于组合和复用 -
洋葱模型:通过compose函数从右到左组合中间件,形成洋葱式的执行流程,每个中间件可以在action到达reducer前后执行逻辑
-
compose函数:工具函数,使用reduce将多个函数组合成一个函数,实现
f(g(h(x)))的效果 -
闭包引用:middlewareAPI中的dispatch使用闭包引用,确保中间件始终调用最新的增强后的dispatch
-
next参数:每个中间件的next参数指向下一个中间件(或最终的store.dispatch),通过调用next(action)将控制权传递下去
-
中间件能力:中间件可以访问store的getState和dispatch,可以修改action、阻止dispatch、执行异步操作等
-
执行顺序:中间件按照传入顺序执行前置逻辑,按照相反顺序执行后置逻辑(洋葱模型的体现)
目录