代码之家  ›  专栏  ›  技术社区  ›  Chance

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

  •  0
  • Chance  · 技术社区  · 6 年前

    我正在尝试设置一个函数,该函数返回映射到相应操作类型名称的切片操作名称的类型安全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
    }
    

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

    0 回复  |  直到 6 年前
        1
  •  1
  •   jcalz    6 年前

    看看你的代码沙盒代码,看起来 keyof typeof slice.actions 没有足够的通用性来表达您要做的事情。尽管 slice T 延伸 Slice ,当你评估 slice.actions :

    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
    

    切片动作 在剩下的代码中,使用 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

    推荐文章