【问题标题】:How can I get a typesafe POJO of actions mapped to a string from a Slice (redux toolkit)如何从切片(redux 工具包)中获取映射到字符串的操作的类型安全 POJO
【发布时间】:2020-09-02 16:42:07
【问题描述】:

我正在尝试设置一个函数,该函数返回一个 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
}

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

【问题讨论】:

    标签: typescript redux typescript-typings redux-toolkit


    【解决方案1】:

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

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

    这很不幸,因为keyof CaseReducerActions&lt;SliceCaseReducers&lt;any&gt;&gt;(又名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() 现在返回Record&lt;keyof T["actions"], string&gt;,这是一个依赖于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

    【讨论】:

    • 我忘了评论这个。太棒了,非常感谢!
    猜你喜欢
    • 2020-07-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多