【问题标题】:How to type a generic type as a subset of an interface in Typescript?如何在 Typescript 中将泛型类型键入为接口的子集?
【发布时间】:2019-12-12 01:27:18
【问题描述】:

是否可以强制将泛型类型(此处为T)作为接口的子集? 示例:

interface Type {
  a: () => number
  b: () => string
}

const func = <T /* Some condition */>(): T => { return ... }

func<Type>(); // OK
func<{a: () => number>(); // OK
func<{a: () => number, c: any}>(); // Error !

我为什么需要这个?

下面的案例构建了一个返回类型为Type的接口。 (TypeReturn 的意思类似于 {a: number, b: string})。

type TypeReturn<T> = { [key in keyof T]: (ReturnType<Type[key]>)[] }

但是 typescript 在Type[key] 抱怨,因为他无法确保T 将成为Type 的键。

【问题讨论】:

  • 您对问题的更新使我使用T extends Partial&lt;Type&gt; 的回答无效。如果你不能做你要求做的事,我不会感到惊讶,因为通常你有TU extends T,你可以使用U,你可以使用T .我不明白你为什么想让{a: () =&gt; number, c: any} 失败。这个问题似乎相关,可能是重复的目标:How to set lower-bound to type parameters in TypeScript?
  • 在对我的回答发表评论时,您说 “我没想到 func&lt;{c: any}&gt;() 能在 func&lt;{a: () =&gt; number, c: any}&gt;(); 不工作时工作。” 前者与Type,但后者确实与Type 有共同之处:{a: () =&gt; number}。如果你想让你的第二个例子 (func&lt;{a: () =&gt; number&gt;();) 工作,正如我上面所说的,我认为你不会让func&lt;{a: () =&gt; number, c: any&gt;(); 失败。但我不是 TypeScript 向导,它有很多的力量,所以... :-)

标签: typescript typescript-typings typescript-generics


【解决方案1】:

您可以使用递归 generic constraint 来模拟您正在寻找的内容,因为 TypeScript 并没有真正的 exact types 会禁止额外的属性。以下是我的做法:

type Exactly<T, U> = T & Record<Exclude<keyof U, keyof T>, never>;
const func = <T extends Exactly<Partial<Type>, T>>(): T => { return null! }

func<Type>(); // OK
func<{ a: () => number }>(); // OK
func<{ a: () => number, c: any }>(); // Error !
//   ~~~~~~~~~~~~~~~~~~~~~~~~~~~
//   Types of property 'c' are incompatible.

这就是你所说的你想要的。请注意,TypeReturn 仍然无法编译,因为即使T extends Exactly&lt;Partial&lt;Type&gt;, T&gt; 表示约束,编译器也只能以这种方式推断T 的具体值;当T 仍然是通用的时,它不理解约束的含义。您需要进一步说服编译器,例如使用内置实用程序类型Extract 以确保K 可以分配给keyof Type

type TypeReturn<T extends Exactly<Partial<Type>, T>> = {
    [K in keyof T]: (ReturnType<Type[Extract<K, keyof Type>]>)[] // okay now
}

type A = TypeReturn<Pick<Type, "a">> // type A = {  a: number[]; }
type B = TypeReturn<Pick<Type, "b">> // type B = {  a: string[]; }
type AB = TypeReturn<Type> // type AB = { a: number[]; b: string [] }
type Oops = TypeReturn<{ a: () => number, c: any }>
//                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~
//   Types of property 'c' are incompatible.

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

Link to code

【讨论】:

  • 令人印象深刻。谢谢!
【解决方案2】:

你真的不应该这样做...但是你可以部分地通过有条件地要求一个特定的字符串参数来做到这一点。不幸的是,这使得错误非常糟糕,迫使您提供一个参数来查看您想要触发的实际错误。 Playground

interface Type {
  a: () => number
  b: () => string
}

declare function func<T extends Partial<Type>>(
    ...check: [keyof T] extends [keyof Type] ? [] :
        ['T keys are not a subset of Type keys']
): T

func<Type>(); // OK
func<{ a: () => number }>(); // OK
func<{ a: number }>(); // Error, not assignable to Type
func<{ a: () => number, c: any }>(); // Error, extra key
    // An argument for 'check' was not provided.

func<never>('') // Argument of type '""' is not assignable to parameter
    // of type '"T keys are not a subset of Type keys"'.(2345)

// However, this won't prevent the following. This cannot be prevented.
const x = { a() { return 1 }, c: false }
const y: Partial<Type> = x
func<typeof y>()

【讨论】:

    猜你喜欢
    • 2021-07-04
    • 2022-11-29
    • 2020-09-04
    • 2021-10-29
    • 2020-02-19
    • 2018-09-15
    • 1970-01-01
    • 2020-10-11
    • 1970-01-01
    相关资源
    最近更新 更多