1

我正在尝试设置一个函数,该函数返回映射到相应操作类型名称的切片操作名称的类型安全 POJO(使用 sliceName/sliceAction 的约定)。该功能非常简单,但让 TypeScript 识别键让我很尴尬。

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

我已经尝试设置了一个代码框

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
}

感谢您花时间阅读本文,我特别感谢任何花时间尝试解决它的人。我知道你的时间很宝贵:)

4

1 回答 1

2

查看您的代码和框代码,似乎keyof typeof slice.actions不足以表达您想要做的事情。即使是扩展slice的泛型类型,当您评估时,编译器也会将其视为:TSliceslice.actionsSlice

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

这很不幸,因为keyof CaseReducerActions<SliceCaseReducers<any>>(aka keyof Slice['actions']) 只是string | number而不是您传递的特定键T

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

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

理想情况下,当您actions在 type 的值上查找属性时T,编译器会将结果视为查找类型 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()现在返回Record<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 代码链接

于 2020-05-16T23:37:32.437 回答