You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为Redux Toolkit编写带TS类型的通用创建项Reducer构建函数?

解决方案

一、修正类型定义:绑定Key与数组元素类型

问题根源是TypeScript无法推断KeysMatching<State, T[]>与State的索引关系,可通过将key作为核心类型参数,让TypeScript自动推导对应的数组元素类型:

写法1:内联条件类型约束

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

// 替换为你的实际State类型
type State = {
  something_set: { id: number; name: string }[];
  another_set: { id: number; value: number }[];
  non_list_prop: string;
};

// 假设getId的类型声明
declare function getId<T extends { id: number }>(list: T[]): number;

function createWithId<K extends keyof State>(
  key: K extends State[K] extends Array<infer T> ? K : never
) {
  return (
    state: State,
    action: PayloadAction<Omit<State[K] extends Array<infer T> ? T : never, "id">>
  ) => {
    const lst = state[key] as Array<{ id: number } & Omit<State[K] extends Array<infer T> ? T : never, "id">>;
    const id = getId(lst);
    lst.push({ ...action.payload, id } as State[K] extends Array<infer T> ? T : never);
  };
}

// 使用示例:自动推导类型,非数组键会报错
const createSomething = createWithId('something_set');
const createAnother = createWithId('another_set');
// const createInvalid = createWithId('non_list_prop'); // 类型错误

写法2:预定义辅助类型(更易读)

// 提取State中所有对应数组类型的键
type StateArrayKeys = {
  [K in keyof State]: State[K] extends Array<unknown> ? K : never;
}[keyof State];

// 根据键获取对应的数组元素类型
type ArrayElement<K extends StateArrayKeys> = State[K] extends Array<infer T> ? T : never;

function createWithId<K extends StateArrayKeys>(key: K) {
  return (
    state: State,
    action: PayloadAction<Omit<ArrayElement<K>, "id">>
  ) => {
    const lst = state[key];
    const id = getId(lst);
    lst.push({ ...action.payload, id } as ArrayElement<K>);
  };
}

这种写法通过预定义辅助类型,让代码更清晰,同时TypeScript会自动验证key的合法性,并正确推导action.payload的类型,解决索引错误问题。

二、Redux Toolkit更优方案:使用createEntityAdapter

如果你的切片用于管理实体集合,RTK内置的createEntityAdapter是更高效的选择,它封装了所有常见的CRUD操作(包括带ID的实体添加),无需重复编写Reducer:

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

// 定义实体类型
type Something = { id: number; name: string };

// 创建实体适配器,自动生成CRUD逻辑
const somethingAdapter = createEntityAdapter<Something>({
  // 可自定义ID提取逻辑,默认取entity.id
  // selectId: (entity) => entity.id,
});

// 初始化规范化状态(包含ids和entities字段)
const initialState = somethingAdapter.getInitialState();

const somethingSlice = createSlice({
  name: 'something',
  initialState,
  reducers: {
    // 直接使用适配器提供的addOne方法,自动处理ID与添加逻辑
    createSomething: somethingAdapter.addOne,
    // 其他预定义方法:addMany、updateOne、deleteOne、upsertOne等
  },
});

export const { createSomething } = somethingSlice.actions;
// 自动生成的selector,用于从状态中获取数据
export const somethingSelectors = somethingAdapter.getSelectors((state) => state.something);

createEntityAdapter不仅减少重复代码,还采用规范化状态结构提升性能,同时提供预定义的selector,比自定义Reducer更健壮、易维护。


内容的提问来源于stack exchange,提问作者Matija Sirk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 09:07:11