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

时间:2020-05-16 20:38:37

标签: typescript redux typescript-typings redux-toolkit

我正在尝试设置一个函数,该函数返回映射到相应动作类型名称(使用sliceName / sliceAction的约定)的Slice动作名称的类型安全POJO。该功能非常简单,但是让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
}

感谢您抽出宝贵的时间阅读本文档,特别感谢您抽出宝贵时间尝试解决该问题。我知道您的时间很宝贵:)

1 个答案:

答案 0 :(得分:1)

查看您的代码和框代码,看来keyof typeof slice.actions的通用性不足以表达您要执行的操作。即使slice是扩展T的泛型类型Slice,当您评估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

理想情况下,当您在类型为actions的值上查询T属性时,编译器会将结果视为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()现在返回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 link to code