React15 - 在ruducer中以对象映射替代switch语句

完全可以使用对象映射来代替 Redux reducer 中的 switch case 语句, 这种写法更加简洁、易维护,也符合函数式编程的思想。以下是几种实现方式:

1. 基础对象映射方式

// 定义 action 类型常量
const TYPES = {
  ADD_TODO: 'ADD_TODO',
  UPDATE_TODO: 'UPDATE_TODO',
  DELETE_TODO: 'DELETE_TODO',
  TOGGLE_TODO: 'TOGGLE_TODO'
};

// 创建 action 处理函数的映射对象
const handlers = {
  [TYPES.ADD_TODO]: (state, action) => ({
    ...state,
    todos: [...state.todos, action.payload]
  }),
  
  [TYPES.UPDATE_TODO]: (state, action) => ({
    ...state,
    todos: state.todos.map(todo =>
      todo.id === action.payload.id ? action.payload : todo
    )
  }),
  
  [TYPES.DELETE_TODO]: (state, action) => ({
    ...state,
    todos: state.todos.filter(todo => todo.id !== action.payload)
  }),
  
  [TYPES.TOGGLE_TODO]: (state, action) => ({
    ...state,
    todos: state.todos.map(todo =>
      todo.id === action.payload
        ? { ...todo, completed: !todo.completed }
        : todo
    )
  })
};

// reducer 使用映射对象
const todoReducer = (state = initialState, action) => {
  const handler = handlers[action.type];
  return handler ? handler(state, action) : state;
};

2. 封装成通用函数

// 创建一个通用的 createReducer 函数
const createReducer = (initialState, handlers) => {
  return (state = initialState, action) => {
    const handler = handlers[action.type];
    return handler ? handler(state, action) : state;
  };
};

// 使用示例
const todoReducer = createReducer(initialState, {
  [TYPES.ADD_TODO]: (state, action) => ({
    ...state,
    todos: [...state.todos, action.payload]
  }),
  
  [TYPES.UPDATE_TODO]: (state, action) => ({
    ...state,
    todos: state.todos.map(todo =>
      todo.id === action.payload.id ? action.payload : todo
    )
  })
});

3. 使用 Redux Toolkit(推荐)

Redux Toolkit 内部已经使用了这种模式:

import { createSlice } from '@reduxjs/toolkit';

const todosSlice = createSlice({
  name: 'todos',
  initialState: {
    todos: []
  },
  reducers: {
    addTodo: (state, action) => {
      state.todos.push(action.payload);
    },
    updateTodo: (state, action) => {
      const index = state.todos.findIndex(todo => todo.id === action.payload.id);
      if (index !== -1) {
        state.todos[index] = action.payload;
      }
    },
    deleteTodo: (state, action) => {
      state.todos = state.todos.filter(todo => todo.id !== action.payload);
    },
    toggleTodo: (state, action) => {
      const todo = state.todos.find(todo => todo.id === action.payload);
      if (todo) {
        todo.completed = !todo.completed;
      }
    }
  }
});

// 自动生成的 action creators
export const { addTodo, updateTodo, deleteTodo, toggleTodo } = todosSlice.actions;
export default todosSlice.reducer;

4. 高级封装 - 支持异步和嵌套

const createReducer = (initialState, handlers) => {
  // 支持嵌套的 action 类型
  const flattenHandlers = (obj, prefix = '') => {
    return Object.keys(obj).reduce((acc, key) => {
      const fullKey = prefix ? `${prefix}/${key}` : key;
      if (typeof obj[key] === 'function') {
        acc[fullKey] = obj[key];
      } else if (typeof obj[key] === 'object') {
        Object.assign(acc, flattenHandlers(obj[key], fullKey));
      }
      return acc;
    }, {});
  };

  const flatHandlers = flattenHandlers(handlers);

  return (state = initialState, action) => {
    // 处理普通 action
    if (flatHandlers[action.type]) {
      return flatHandlers[action.type](state, action);
    }
    
    // 处理异步 action 的三个状态
    const [baseType, status] = action.type.split('/');
    if (status && flatHandlers[`${baseType}/${status}`]) {
      return flatHandlers[`${baseType}/${status}`](state, action);
    }
    
    return state;
  };
};

// 使用示例
const userReducer = createReducer(
  { data: null, loading: false, error: null },
  {
    FETCH_USER: {
      PENDING: (state) => ({ ...state, loading: true, error: null }),
      FULFILLED: (state, action) => ({
        ...state,
        loading: false,
        data: action.payload
      }),
      REJECTED: (state, action) => ({
        ...state,
        loading: false,
        error: action.payload
      })
    },
    UPDATE_USER: (state, action) => ({
      ...state,
      data: { ...state.data, ...action.payload }
    })
  }
);

5. TypeScript 版本

type ReducerHandlers<S, A extends Action = AnyAction> = {
  [key: string]: (state: S, action: A) => S;
};

function createReducer<S>(
  initialState: S,
  handlers: ReducerHandlers<S>
): (state: S | undefined, action: AnyAction) => S {
  return (state: S = initialState, action: AnyAction): S => {
    const handler = handlers[action.type];
    return handler ? handler(state, action) : state;
  };
}

// 使用示例
interface Todo {
  id: string;
  text: string;
  completed: boolean;
}

interface TodoState {
  todos: Todo[];
}

const todoReducer = createReducer<TodoState>(
  { todos: [] },
  {
    [TYPES.ADD_TODO]: (state, action: PayloadAction<Todo>) => ({
      ...state,
      todos: [...state.todos, action.payload]
    }),
    [TYPES.TOGGLE_TODO]: (state, action: PayloadAction<string>) => ({
      ...state,
      todos: state.todos.map(todo =>
        todo.id === action.payload
          ? { ...todo, completed: !todo.completed }
          : todo
      )
    })
  }
);

优点

  1. 更清晰的代码结构:每个 action 类型对应一个处理函数
  2. 易于测试:可以单独测试每个 handler 函数
  3. 易于扩展:添加新的 action 类型只需添加新的键值对
  4. 类型安全:在 TypeScript 中可以获得更好的类型推断
  5. 性能更好:对象属性查找比 switch 语句更快

注意事项

  • 确保处理了未知的 action 类型(返回原状态)
  • 保持 reducer 的纯函数特性
  • 不要直接修改 state(除非使用 Redux Toolkit 的 Immer)

这种模式在实际开发中非常实用,特别是配合 Redux Toolkit,可以让代码更加简洁和可维护。

posted @ 2026-03-15 22:43  箫笛  阅读(1)  评论(0)    收藏  举报