Búsqueda de sitios web

¿Por qué se introdujo el tipo `never` en TypeScript?


Ahora he buscado en internet una explicación de POR QUÉ se introdujo el tipo never. Tanto el manual de TypeScript como TypeScript Deep Dive explican qué es y algunos otros artículos. Sin embargo, ninguno de ellos explica POR QUÉ usarías el tipo never y qué resuelve. La mayoría de los ejemplos muestran código donde el compilador ya infiere never, así que, ¿por qué tengo que asignar el tipo? ¿Qué resuelve? ¿Es “solo” para señalar al intellisense qué puedo hacer después (es decir, nada, lo cual podría ser lo suficientemente útil)? ¿Y para qué usa el compilador esta información? Como siempre, las explicaciones de QUÉ y CÓMO pueden ayudar a las personas a hacer cosas, pero solo el POR QUÉ les hace entender.

Respuesta #1

El tipo never es la representación en TypeScript del tipo inferior de la teoría de tipos. Así que si estás buscando motivación y usos de never en TypeScript, podrías querer investigar sobre el tipo inferior en ciencias de la computación en general.

La respuesta canónica a "por qué se introdujo never en TypeScript" probablemente se encuentre mejor al analizar los comentarios en la solicitud de extracción que lo introdujo y los dos problemas que aborda: una solicitud de un tipo inferior y una solicitud para manejar mejor los retornos de funciones inalcanzables.

El uso principal para los desarrolladores es tener un tipo para funciones (o secciones de funciones) que no devuelven ningún valor en absoluto, ni siquiera undefined. Compara estas funciones:

function u(): undefined {
    return;
}
const uRet = u();
console.log(typeof uRet); // undefined

function v(): void { }
const vRet = v();
console.log(typeof vRet); // undefined

que devuelven valores undefined, con estas funciones:

function throws(): never {
    throw new Error();
}
const tRet = throws();
console.log(typeof tRet); // does not run

function loops(): never {
    while (true) { }
}
const lRet = loops();
console.log(typeof lRet); // does not run

que no devuelven valores en absoluto, debido a una excepción o un bucle infinito. El tipo never permite tanto a los desarrolladores como al compilador razonar sobre secciones de código que nunca pueden ejecutarse.

Hay otros usos para never, pero no voy a intentar enumerarlos, ya que ya estás al tanto de los ejemplos de código. Es esclarecedor examinar la biblioteca estándar de TypeScript para encontrar los lugares donde aparece never.

Finalmente, cuando preguntas

La mayoría de los ejemplos muestran código, donde el compilador ya infiere never, entonces ¿por qué tengo que asignar el tipo?

ten en cuenta que incluso en los casos en que el compilador infiere never por ti, es útil que exista en el lenguaje, de la misma manera que string es útil incluso cuando es inferido por el compilador. Esto parece una pregunta diferente sobre cuándo podrías querer anotar explícitamente los tipos en lugar de dejar que el compilador los infiera, y viceversa. Pero esta publicación ya es larga y probablemente esto ya esté respondido en otro lugar.

¡Espero que eso ayude; buena suerte!

Edición: Hay muchas razones por las que un programador usaría never, aunque muchos programadores podrían nunca necesitar usarlo. Permítanme enumerar algunas razones que se me ocurren:

  • Si estás escribiendo definiciones de tipos para una biblioteca de JavaScript donde una función lanza una excepción en lugar de devolver un valor, realmente no puedes hacer eso sin never.

  • Los parámetros de tipo genérico por defecto son {} cuando el compilador no puede inferirlos. Es como un "fallo abierto", ya que {} es compatible con (casi) todos los valores. Hay casos en los que preferirías que "fallara cerrado" y que no fuera compatible con ningún valor. Puedes usar never como parámetro por defecto.

  • La unión de cualquier tipo T con never es solo T, y la intersección de T con never es never. Estas reglas (entre otras) permiten a un desarrollador construir funciones de tipos bastante sofisticadas que serían más difíciles o imposibles sin never. Por ejemplo, aquí está Diff<T,U>:

    type Diff<T extends string, U extends string> =
      ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T];
    

    Es un tipo de función que toma una unión de literales de cadena T y una unión de literales de cadena U, y devuelve una unión de todos los valores en T que no están presentes en U.

    Puedes usarlo para, entre otras cosas, eliminar una propiedad de un tipo:

    type Foo = { a: string, b: number, c: boolean };
    type FooWithoutB = Pick<Foo, Diff<keyof Foo, 'b'>>;
    // equivalent to { a: string, c: boolean }
    

Es algo así como el número cero en matemáticas. No lo necesitas; muchas culturas se las arreglaban bien sin el concepto de cero. Pero es increíblemente útil y te permite expresar ideas fácilmente que de otro modo serían engorrosas o imposibles.

¿Con suerte eso es un poco más convincente? De todos modos, buena suerte de nuevo.

Artículos relacionados