Typescript, Genéricos - omitir propiedades null/undefined de un tipo
He intentado escribir un tipo en TypeScript que excluya null y undefined de un tipo:
type NonNull<T> = { [P in keyof T]: T[P] extends undefined | null ? never : T[P]; }Por lo que entiendo, debería eliminar las propiedades nulas o indefinidas del tipo T. Sin embargo, cuando intento usarlo:
let test2: NonNull<{a: null, b: number}> = { b: 12 }Lanza un error:
Property 'a' is missing in type '{ b: number; }' but required in type 'NonNull<{ a: null; b: number; }>'¿Por qué el compilador de TypeScript se queja de que falta la propiedad 'a', cuando en realidad debería estar excluida del tipo (ya que declaro 'a' como null en la plantilla genérica)? Si estoy entendiendo la construcción incorrectamente, ¿qué es lo que realmente hace?
como se explicó en el código anterior.
Respuesta #1
Si quieres omitir claves de propiedad de un tipo T en un tipo mapeado, de la forma {[P in 〈lado de la clave〉]: 〈lado del valor〉}, entonces necesitas suprimirlas realmente en el lado de la clave, no en el lado del valor. Si solo estás comprobando T[P] extends undefined | null en el lado del valor, entonces no podrás suprimir la propiedad.
Está bien, entonces queremos omitir claves donde los valores de las propiedades sean null o undefined. Una manera de hacerlo es usar el remapeo de claves en tipos mapeados a través de as. Si remapeas una clave a never, la clave se omite del resultado del tipo mapeado. Se ve así:
type NonNull<T> = {
[P in keyof T as T[P] extends undefined | null ? never : P]: T[P]
}
type Test = NonNull<{ a: null, b: number }>;
/* type Test = { b: number; }*/
Y puedes ver que funciona como se desea.
Tenga en cuenta que esto no es cómo funciona el tipo de utilidad Omit<T, K>, en parte porque la reasignación de claves no existía cuando se introdujo ese tipo. Omit se ve así
type Omit<T, K extends string | number | symbol> =
{ [P in Exclude<keyof T, K>]: T[P]; }
Usar el reasignamiento de teclas es fácil porque nos permite decidir si mantener o no una tecla P mirando T[P] directamente. Si tuvieras que escribir una versión que funcionara sin reasignamiento, sería más complicado, ya que primero tendríamos que identificar las teclas a omitir:
type NullableKeys<T> = {
[P in keyof T]-?: T[P] extends undefined | null ? P : never
}[keyof T]
y luego pásalos a Omit o a un equivalente:
type NonNull<T> = Omit<T, NullableKeys<T>>
type Test = NonNull<{ a: null, b: number }>;
/* type Test = { b: number; }*/
Puedes ver que esto también funciona. En ambos casos, el T[P] extends undefined | null termina teniendo efecto en el lado de la clave del tipo mapeado y no en el lado del valor.
Enlace de Playground al código
