如何从 Slice(redux 工具包)中获取映射到字符串的类型安全的 POJO 操作

How can I get a typesafe POJO of actions mapped to a string from a Slice (redux toolkit)

我正在尝试设置一个函数,该函数 returns 将 Slice 的动作名称的类型安全 POJO 映射到相应的动作类型名称(使用 sliceName/sliceAction 的约定)。该功能非常简单,但让 TypeScript 识别键让我尴尬地绊倒了。

我正在按照 Redux Toolkit's tutorial 中的示例进行操作,但我想看看这是否可行。理想情况下,我将能够将该函数与 Redux-Saga 或 Redux-Observable 结合使用,以避免使用字符串文字作为操作类型。

我尝试设置了 codesandbox

const getActions = <T extends Slice>(slice: T) => {
  const keys = Object.keys(slice.actions) as Array<keyof typeof slice.actions>;
  return keys.reduce(
    (accumulator, key) => {
      accumulator[key] = `${slice.name}/${key}`;
      return accumulator;
    },
    {} as Record<keyof typeof slice.actions, string>
  );
};

目的是能够像这样切片:

const issuesDisplaySlice = createSlice({
  name: "issuesDisplay",
  initialState,
  reducers: {
    displayRepo(state, action: PayloadAction<CurrentRepo>) {
      //...
    },
    setCurrentPage(state, action: PayloadAction<number>) {
      //...
    }
  }
});

并获取 TypeScript 以将输出识别为:

{
  displayRepo: string,
  setCurrentPage: string
}

感谢您花时间阅读本文,我特别感谢所有花时间尝试解决此问题的人。我知道你的时间很宝贵:)

查看您的 codesandbox 代码,keyof typeof slice.actions 似乎不够通用,无法表达您要执行的操作。即使 slice 是扩展 Slice 的泛型类型 T,当您评估 slice.actions 时,编译器将其视为只是 Slice:

const actions = slice.actions; // CaseReducerActions<SliceCaseReducers<any>>

这很不幸,因为 keyof CaseReducerActions<SliceCaseReducers<any>>(又名 keyof Slice['actions'])只是 string | number 而不是您在 T 上传递的特定密钥:

type KeyofSliceActions = keyof Slice['actions'];
// type KeyofSliceActions = string | number

将泛型值扩大到它们对 属性 查找的约束可能对性能有好处,但会导致一些不良情况,例如这种情况。有关相关问题,请参阅 microsoft/TypeScript#33181

理想情况下,当您在 T 类型的值上查找 actions 属性 时,编译器会将结果视为 lookup type T['actions'] 反而。这不会 自动发生 ,但您可以要求编译器使用类型注释来完成它:

const genericActions: T['actions'] = slice.actions;  // this is acceptable

现在,如果您将其余代码中的 slice.actions 实例替换为 genericActions,输入将按您希望的方式工作:

const getActions = <T extends Slice>(slice: T) => {
  const genericActions: T['actions'] = slice.actions;  // this is acceptable
  const keys = Object.keys(genericActions) as Array<keyof typeof genericActions>;
  return keys.reduce(
    (accumulator, key) => {
      accumulator[key] = `${slice.name}/${key}`;
      return accumulator;
    },
    {} as Record<keyof typeof genericActions, string>
  );
};

// const getActions: <T extends Slice<any, SliceCaseReducers<any>, string>>(
//   slice: T) => Record<keyof T["actions"], string>

(旁白:keyof typeof genericActions可以简写为keyof T['actions']。)可以看到getActions()现在returnsRecord<keyof T["actions"], string>,一个依赖的类型T.

让我们看看它是否有效:

const actions = getActions<typeof issuesDisplaySlice>(issuesDisplaySlice);
// const actions: Record<"displayRepo" | "setCurrentPage" | "setCurrentDisplayType", string>

actions.displayRepo.toUpperCase(); // okay
actions.displayTypo.toUpperCase(); // error!
// ---> ~~~~~~~~~~~
// Property 'displayTypo' does not exist on type 
// 'Record<"displayRepo" | "setCurrentPage" | "setCurrentDisplayType", string>'.
// Did you mean 'displayRepo'?

我觉得不错。好的,希望有所帮助;祝你好运!

Playground link to code