【问题标题】:Combine generic interfaces with two generic types into one type with only one generic type将具有两种泛型类型的泛型接口组合成一种仅具有一种泛型类型的类型
【发布时间】:2019-12-06 03:34:39
【问题描述】:

就我而言,我正在使用来自 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&lt;T&gt; 已经崩溃了。 就我而言,我不希望reducer 函数担心ActionTypes&lt;T, K&gt;K 是什么类型。

我知道我可以将 keyof T 添加到 action: ActionTypes&lt;T, keyof T&gt; 但这会破坏这一行的类型检查:

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

因为[action.payload.name] 的类型可能与action.payload.value 不同

实际上,如果 ActionType&lt;T, K extends keyof T&gt; 只有一种泛型类型 (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。)

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

【问题讨论】:

  • type ActionTypes = IActionOne | IActionTwo;
  • 正如我在帖子中已经说过的,这不会有帮助,因为它会破坏类型检查..

标签: reactjs typescript generics typescript-generics


【解决方案1】:

在这种情况下,您似乎希望action 参数成为所有可能的操作类型的联合...对于keyof T 中的每个可能的K。如果是这样,我将使用distributive conditional type 将联合keyof T 分解为每个单独的文字K,并获得所有ActionTypes&lt;T, K&gt; 的联合:

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&lt;T&gt;,将ActionOne&lt;T, K&gt;分配给所有K,然后将ActionTwo&lt;T, K&gt;单独分配给所有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

【讨论】:

  • 感谢您的详细解答!不幸的是,一旦ActionOneActionTwo 具有不同的结构,例如向ActionOne 的有效负载添加一个新属性:payload: { name: K; value: T[K], test: string };。在reducer() 中,action.test 属性以防 UPDATE_VALUE_ONE 将不存在。所以是的,对于给你它的确切例子,它是有效的,但遗憾的是不是一般......
  • 我没有预料到这个方向的概括,抱歉。我将编辑我的答案以适用于这种情况。
  • 抱歉,回复晚了。我刚刚试了一下,它可以正常工作,谢谢!仍然看起来有点混乱,因为您必须为依赖于 K 的每个操作声明一个新的 type (在我的情况下,它不仅仅是两个操作..)我真的想知道是否没有更好的选择
猜你喜欢
  • 2020-06-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多