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

Posted

技术标签:

【中文标题】如何从切片(redux 工具包)中获取映射到字符串的操作的类型安全 POJO【英文标题】:How can I get a typesafe POJO of actions mapped to a string from a Slice (redux toolkit) 【发布时间】: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

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

【问题讨论】:

【参考方案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

【讨论】:

我忘了评论这个。太棒了,非常感谢!

以上是关于如何从切片(redux 工具包)中获取映射到字符串的操作的类型安全 POJO的主要内容,如果未能解决你的问题,请参考以下文章

Redux 工具包中多个切片中的操作

如何从 React 组件 Redux Toolkit 中的 createAsyncThunk 获取结果

如何正确使用 redux 工具包的 preloadedState 和 createSlice?

redux-toolkit 在来自另一个 thunk reducer 的同一切片中使用 actionCreater

将 props 从 redux 映射到组件状态

如何使用react-redux将redux商店中封装的ui状态映射到道具?