将具有两种泛型类型的泛型接口合并为一种只有一种泛型类型的类型

Combine generic interfaces with two generic types into one type with only one generic type

就我而言,我正在使用来自 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 函数担心 ActionTypes<T, K>K 是什么类型。

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

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

因为 [action.payload.name] 的类型可能不同于 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的interface/type中使用时如何“隐藏”? (特别是如果 K 无论如何都依赖于 K extends keyof T T。)

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

在这种情况下,您似乎希望 action 参数成为所有可能的操作类型的联合...对于 keyof T 中的每个可能的 K。如果是这样,我将使用 distributive conditional type 将并集 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>;

(更新:我修改了 AllPossibleActionTypes<T> 的上述版本,为所有 K 分发 ActionOne<T, K>,然后为所有 K 单独分发 ActionTwo<T, K> ],然后再统一。注意,当你指定一个具体类型T时,这是完全相同的类型,但当T仍然是一个未解析的泛型时编译器对它们的解释不同。具体来说,在旧版本中,编译器会等到它知道 T 才将类型拆分为 ActionOneActionTwo 变体;现在编译器从开始。)

那么 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

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

Link to code