如何在 Typescript 中将泛型类型键入为接口的子集?

How to type a generic type as a subset of an interface in Typescript?

是否可以将通用类型(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 的 return 类型构建了一个接口。 (TypeReturn 的意思类似于 {a: number, b: string})。

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

但是打字稿在 Type[key] 抱怨,因为他不能确保 T 将成为 Type.

的键

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

您可以使用递归 generic constraint to simulate what you're looking for, since TypeScript doesn't really have 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<Partial<Type>, T> 表示约束,编译器也只能以这种方式推理 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