首页 > 解决方案 > 将具有两种泛型类型的泛型接口组合成一种仅具有一种泛型类型的类型

问题描述

就我而言,我正在使用React 的useReducer,但我的问题是纯粹的打字稿问题。

采取以下措施:

export interface State<T> {
  values: T;
  someOtherProp: boolean;
}

export interface ActionOne<T, K extends keyof T> {
  type: "UPDATE_VALUE_ONE";
  payload: { name: K; value: T[K] };
}


export interface ActionTwo<T, K extends keyof T> {
  type: "UPDATE_VALUE_TWO";
  payload: { name: K; value: T[K] };
}

现在两个动作接口组合成一个联合类型:

type ActionTypes<T, K extends keyof T> = ActionOne<T, K> | ActionTwo<T, K>

在下一步中,函数将ActionTypes用作其参数的类型:

export const reducer = <T>(
  state: State<T>,
  action: ActionTypes<T> // This already breaks because the second generic type is missing
): State<T> =>
{ 
   switch (action.type) {
     case "UPDATE_VALUE_ONE":
      return {
        ...state,
        values: { ...state.values, [action.payload.name]: action.payload.value}
      };
     case "UPDATE_VALUE_ONE":
    // ...
  }
}

正如您在代码中看到的那样,注释ActionTypes<T>已经被破坏了。就我而言,我不希望reducer函数担心是什么类型KActionTypes<T, K>

我知道我可以添加keyof Taction: ActionTypes<T, keyof T>但这会破坏这一行的类型检查:

values: { ...state.values, [action.payload.name]: action.payload.value}

因为[action.payload.name]cane的类型不同于action.payload.value

ActionType<T, K extends keyof T>实际上,如果只有一种泛型类型 ( )会更理想T

如果ActionOneActionTwo由函数返回,而不是直接将它们作为对象传递,它会通过声明函数内联来工作:

type ActionTypes<T> =
 | (<T, K extends keyof T>() => ActionOne<T, K>)
 | (<T, K extends keyof T>() => ActionTwo<T, K>);`

问题

K当接口的第二种泛型类型用于仅声明一个泛型类型的另一个接口/类型时,如何“隐藏”该接口的第二个泛型类型的信息T?(特别是如果K无论如何都依赖于TK extends keyof T

对于泛型函数,这可以通过内联声明来解决,那么接口如何解决呢?

标签: reactjstypescriptgenericstypescript-generics

解决方案


在这种情况下,您似乎希望action参数是所有可能的动作类型的联合......对于每个可能K的 in keyof T。如果是这样,我会使用分布式条件类型将联合keyof T分解为每个单独的文字K,并获得所有的联合ActionTypes<T, K>

type SomeActionOne<T, K extends keyof T = keyof T> = K extends any
  ? ActionOne<T, K>
  : never;
type SomeActionTwo<T, K extends keyof T = keyof T> = K extends any
  ? ActionTwo<T, K>
  : never;
type AllPossibleActionTypes<T> = SomeActionOne<T> | SomeActionTwo<T>;

(更新:我修改了上面的版本为 allAllPossibleActionTypes<T>分发,然后分别为 all分发,然后将它们统一。请注意,当您指定具体类型时,这是完全相同的类型,但 when仍然是未解决的泛型编译器对它们的解释不同。具体来说,在旧版本中,编译器会等到它知道将类型拆分为和变体;现在编译器从一开始就知道这一点。)ActionOne<T, K>KActionTwo<T, K>KTTTActionOneActionTwo

那么签名reducer将是:

export const reducer = <T>(
  state: State<T>,
  action: AllPossibleActionTypes<T>
): State<T> => { ... }

你可以看到当你打电话时你得到了合理的暗示reducer()

reducer(
  { values: { a: "a", b: 1, c: true }, someOtherProp: true },
  { type: "UPDATE_VALUE_TWO", payload: { name: "b", value: 3 } }
); // hinted for value: number when you type in "b" for name

好的,希望有帮助;祝你好运!

链接到代码


推荐阅读