实现Redux中间件机制

手写实现Redux的applyMiddleware函数,理解中间件的洋葱模型和函数组合原理

问题

Redux中间件是Redux架构中的重要组成部分,它允许我们在action被dispatch到reducer之前进行拦截和处理。需要实现一个applyMiddleware函数,支持:

  1. 接收多个中间件函数
  2. 按照洋葱模型执行中间件
  3. 增强store的dispatch方法
  4. 支持中间件访问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、执行异步操作等

  • 执行顺序:中间件按照传入顺序执行前置逻辑,按照相反顺序执行后置逻辑(洋葱模型的体现)