实现redux-thunk中间件

手写实现Redux的异步action处理中间件redux-thunk,支持dispatch函数类型的action

问题

Redux默认只支持dispatch普通对象类型的action,无法处理异步操作。redux-thunk是一个Redux中间件,它允许我们dispatch一个函数而不是普通对象,这个函数可以执行异步操作,并在适当的时候dispatch真正的action。

需要实现一个redux-thunk中间件,使得:

  • 如果dispatch的action是函数,则执行该函数,并传入dispatch和getState
  • 如果dispatch的action是普通对象,则正常传递给下一个中间件

解答

/**
 * redux-thunk 中间件实现
 * @param {Object} store - Redux store对象,包含dispatch、getState等方法
 * @returns {Function} 返回中间件函数
 */
function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    // 如果action是函数,则执行该函数
    // 并将dispatch、getState和额外参数传递给它
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }
    
    // 如果action不是函数,则传递给下一个中间件
    return next(action);
  };
}

// 默认的thunk中间件(不带额外参数)
const thunk = createThunkMiddleware();

// 支持自定义额外参数的thunk
thunk.withExtraArgument = createThunkMiddleware;

// 导出
export default thunk;

使用示例

import { createStore, applyMiddleware } from 'redux';
import thunk from './redux-thunk';

// 定义reducer
function counterReducer(state = { count: 0 }, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

// 创建store并应用thunk中间件
const store = createStore(counterReducer, applyMiddleware(thunk));

// 普通的同步action creator
const increment = () => ({ type: 'INCREMENT' });

// 异步action creator(返回函数)
const incrementAsync = () => {
  return (dispatch, getState) => {
    setTimeout(() => {
      console.log('当前状态:', getState());
      dispatch(increment());
    }, 1000);
  };
};

// 带条件判断的异步action
const incrementIfOdd = () => {
  return (dispatch, getState) => {
    const { count } = getState();
    if (count % 2 !== 0) {
      dispatch(increment());
    }
  };
};

// 异步请求示例
const fetchUserData = (userId) => {
  return async (dispatch, getState) => {
    dispatch({ type: 'FETCH_USER_REQUEST' });
    
    try {
      const response = await fetch(`/api/users/${userId}`);
      const data = await response.json();
      dispatch({ type: 'FETCH_USER_SUCCESS', payload: data });
    } catch (error) {
      dispatch({ type: 'FETCH_USER_FAILURE', error: error.message });
    }
  };
};

// 使用
store.dispatch(increment());           // 同步dispatch
store.dispatch(incrementAsync());      // 异步dispatch
store.dispatch(incrementIfOdd());      // 条件dispatch
store.dispatch(fetchUserData(123));    // 异步请求

// 使用额外参数的示例
const api = {
  fetchUser: (id) => fetch(`/api/users/${id}`).then(r => r.json())
};

const thunkWithApi = thunk.withExtraArgument(api);
const store2 = createStore(counterReducer, applyMiddleware(thunkWithApi));

const loadUser = (userId) => {
  return async (dispatch, getState, api) => {
    const user = await api.fetchUser(userId);
    dispatch({ type: 'USER_LOADED', payload: user });
  };
};

store2.dispatch(loadUser(123));

关键点

  • 中间件的柯里化结构store => next => action 三层函数嵌套,符合Redux中间件规范

    • 第一层接收store对象(包含dispatch和getState)
    • 第二层接收next函数(下一个中间件或原始dispatch)
    • 第三层接收action并进行处理
  • 类型判断:通过 typeof action === 'function' 判断action是否为函数类型

  • 函数执行:如果是函数类型,执行该函数并传入 dispatchgetState,使函数内部可以:

    • 访问当前state(通过getState)
    • 派发新的action(通过dispatch)
    • 执行异步操作
  • 透传机制:如果不是函数类型,调用 next(action) 将action传递给下一个中间件

  • 返回值处理:函数类型的action执行结果会被返回,支持Promise等异步操作的链式调用

  • 额外参数支持:通过 withExtraArgument 方法可以注入自定义依赖(如API服务),增强可测试性