Tipos condicionales de TypeScript: filtre las propiedades de solo lectura/seleccione solo las propiedades requeridas



typescript2.8 conditional-types (1)

Usando los nuevos tipos condicionales en TypeScript (o tal vez otra técnica), ¿hay alguna manera de elegir solo ciertas propiedades de una interfaz en función de sus modificadores? Por ejemplo, tener ...

interface I1 {
    readonly n: number
    s: string
}

Me gustaría crear un nuevo tipo basado en el anterior que se ve así:

interface I2 {
    s: string
}

Actualización 2018-10: @MattMcCutchen ha descubierto que es posible detectar campos de readonly (invalidando el pasaje tachado a continuación), como se muestra en esta respuesta . Aquí hay una manera de construirlo:

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];

Si desea extraer los campos de escritura de una interfaz, puede usar la definición de WritableKeys anterior y Pick juntos:

interface I1 {
    readonly n: number
    s: string
}

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

¡Hurra!

Solo para readonly , no creo que puedas extraerlos. He examinado este problema antes y no fue posible entonces; Y no creo que nada haya cambiado.

Como el compilador no comprueba las propiedades de readonly , siempre puede asignar un {readonly n: number} a un {n: number} y viceversa. Y por lo tanto, la obvia verificación de tipo condicional TSv2.8 no funciona. Si, por ejemplo, {n: number} no se consideraba asignable a {readonly n: number} , podría hacer algo como:

// 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 {} 🙁

Pero no puedes. Hay una discusión interesante sobre esto en un tema de GitHub originalmente llamado "los modificadores de readonly son una broma" .

¡Lo siento! Buena suerte.

Para propiedades opcionales, puede detectarlas y, por lo tanto, extraerlas o excluirlas. La idea aquí es que {} extiende {a?: string} , pero {} no extiende {a: string} o incluso {a: string | undefined} {a: string | undefined} . A continuación, le mostramos cómo puede crear una forma de eliminar propiedades opcionales de un tipo:

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} 🙂

Eso está bien.

Finalmente, no sé si quieres poder hacer cosas con los modificadores de propiedad de clase como public , private , protected y abstract , pero dudaría de ello. Sucede que las propiedades de clase private y protected se pueden excluir con bastante facilidad, ya que no están presentes en 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} 🙂

Pero extraer las propiedades private o protected puede ser imposible, por la misma razón que excluirlas es tan fácil: keyof Foo no las tiene. Y para todo esto, incluido el abstract , no puede agregarlos a las propiedades en alias de tipo (son modificadores de clase), por lo que no hay mucho que se me ocurra para tocarlos.

De acuerdo, espero que eso ayude.





conditional-types