TypeScript 条件类型 - 过滤掉只读属性/只选择必需的属性

TypeScript conditional types - filter out readonly properties / pick only required properties

使用 TypeScript 中的新条件类型(或者可能是另一种技术),有没有办法根据修饰符从接口中仅选择某些属性?例如,有...

interface I1 {
    readonly n: number
    s: string
}

我想在之前的类型的基础上创建一个新类型,如下所示:

interface I2 {
    s: string
}

2018-10 更新:@MattMcCutchen has figured out that it is possible to detect readonly fields (invalidating the struck-out passage below), as shown in 。这是一种构建它的方法:

type IfEquals<X, Y, A=X, B=never> =
  (<T>() => T extends X ? 1 : 2) extends
  (<T>() => T extends Y ? 1 : 2) ? A : B;

type WritableKeys<T> = {
  [P in keyof T]-?: IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, P>
}[keyof T];

type ReadonlyKeys<T> = {
  [P in keyof T]-?: IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, never, P>
}[keyof T];

如果要从接口中提取可写字段,可以使用上面的WritableKeys定义和Pick一起使用:

interface I1 {
    readonly n: number
    s: string
}

type I2 = Pick<I1, WritableKeys<I1>>; 
// equivalent to { s: string; }

万岁!

对于 readonly,我认为您无法提取这些。我已经 looked at this issue before 但那是不可能的;而且我认为没有任何改变。

the compiler doesn't soundly check readonly properties 以来,您始终可以将 {readonly n: number} 分配给 {n: number} 和 vice-versa。因此,明显的 TSv2.8 条件类型检查不起作用。例如,如果 {n: number} 不被认为可分配给 {readonly n: number},那么您可以执行以下操作:

// does not work, do not try this
type ExcludeReadonlyProps<T> = Pick<T,
  { [K in keyof T]-?:
    ({ readonly [P in K]: T[K] } extends { [P in K]: T[K] } ? never : K)
  }[keyof T]>

type I2 = ExcludeReadonlyProps<I1> // should be {s: string} but is {} 

但是你不能。 GitHub issue originally named "readonly modifiers are a joke" 中对此有一些有趣的讨论。

对不起!祝你好运。


对于可选属性,您确实可以检测到它们并因此提取或排除它们。这里的见解是 {} 扩展了 {a?: string},但 {} 没有扩展 {a: string} 甚至 {a: string | undefined}。以下是如何构建一种从类型中删除可选属性的方法:

type RequiredKeys<T> = { [K in keyof T]-?:
  ({} extends { [P in K]: T[K] } ? never : K)
}[keyof T]

type OptionalKeys<T> = { [K in keyof T]-?:
  ({} extends { [P in K]: T[K] } ? K : never)
}[keyof T]

type ExcludeOptionalProps<T> = Pick<T, RequiredKeys<T>>

type I3 = { 
  a: string, 
  b?: number, 
  c: boolean | undefined
}

type I4 = ExcludeOptionalProps<I3>;
// {a: string; c: boolean | undefined} 

很好。


最后,我不知道你是否希望能够使用 class-only 属性 修饰符,如 publicprivate、[=32] =],和 abstract,但我对此表示怀疑。 privateprotected class 属性可以很容易地 排除 ,因为它们不存在于 keyof 中:

class Foo {
  public a = ""
  protected b = 2
  private c = false
}
type PublicOnly<T> = Pick<T, keyof T>; // seems like a no-op but it works
type PublicFoo = PublicOnly<Foo>; // {a: string} 

但是提取 privateprotected 属性可能是不可能的,原因与排除它们非常容易一样:keyof Foo没有它们。对于所有这些,包括 abstract,您不能将它们添加到类型别名中的属性(它们是 class-only 修饰符),所以我想不出太多办法来触及它们。


好的,希望对您有所帮助。