Cuando vi por primera vez la palabra nunca, pensé en lo inútil que aparecía el tipo en TypeScript. Con el tiempo, al sumergirme más en ts, comencé a comprender cuán poderosa es esta palabra. Y este poder nace de casos de uso del mundo real que pretendo compartir con el lector. A quién le importa, bienvenido al gato.
Lo que nunca es
Si nos sumergimos en la historia, veremos que el tipo nunca apareció en los albores de TypeScript versión 2.0, con una
descripción bastante
modesta de su propósito. Si vuelve a contar breve y libremente la versión de los desarrolladores ts, entonces el tipo nunca es un tipo primitivo que representa un signo de valores que nunca sucederán. O bien, un signo de funciones que nunca devolverán valores, ya sea por su bucle, por ejemplo, un bucle infinito o por su interrupción. Y para mostrar claramente la esencia de lo que se dijo, propongo ver un ejemplo a continuación:
function error(message: string): never { throw new Error(message); } function infiniteLoop(): never { while (true) { } } function infiniteRec(): never { return infiniteRec(); }
Quizás debido a tales ejemplos, tuve la primera impresión de que el tipo es necesario para mayor claridad.
Sistema de tipo
Ahora puedo decir que la rica fauna del sistema de tipos en TypeScript nunca se debe. Y en apoyo de mis palabras, daré varios tipos de bibliotecas de lib.es5.d.ts
type Exclude<T, U> = T extends U ? never : T; type Extract<T, U> = T extends U ? T : never; type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>; type NonNullable<T> = T extends null | undefined ? never : T; type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
De mis tipos con nunca, daré mi favorito: GetNames, un análogo mejorado de keyof:
type GetNames<FromType, KeepType = any, Include = true> = { [K in keyof FromType]: FromType[K] extends KeepType ? Include extends true ? K : never : Include extends true ? never : K }[keyof FromType];
Monitorear cambios futuros
Cuando la vida del proyecto no es fugaz, el desarrollo adquiere un carácter especial. Me gustaría tener control sobre los puntos clave en el código, para tener garantías de que usted o los miembros de su equipo no olvidarán arreglar un código vital cuando lleve tiempo. Tales secciones de código son particularmente manifiestas cuando hay un estado o enumeración de algo. Por ejemplo, enumerar un conjunto de acciones en una entidad. Sugiero mirar un ejemplo para comprender el contexto de lo que está sucediendo.
El código anterior se simplifica tanto como sea posible solo para centrarse en un punto importante: el tipo de AdminAction se define en otro proyecto e incluso es posible que su equipo no lo acompañe. Como el proyecto durará mucho tiempo, es necesario proteger su ActionEngine de los cambios en el tipo de AdminAction sin su conocimiento. TypeScript ofrece varias recetas para resolver este problema, una de las cuales es usar el tipo nunca. Para hacer esto, necesitamos definir NeverError y usarlo en el método doAction.
class NeverError extends Error {
Ahora agregue un nuevo valor "BLOCK" a AdminAction y obtenga un error en el momento de la compilación: el argumento del tipo '"BLOCK"' no se puede asignar al parámetro del tipo 'never'.ts (2345).
En principio, lo logramos. Vale la pena mencionar un punto interesante de que la construcción del interruptor nos protege de cambiar los elementos de AdminAction o eliminarlos del conjunto. Desde la práctica, puedo decir que realmente funciona como se esperaba.
Si no desea introducir la clase NeverError, puede controlar el código declarando una variable de tipo nunca. Así:
type AdminAction = "CREATE" | "ACTIVATE" | "BLOCK"; class ActionEngine { doAction(action: AdminAction) { switch (action) { case "CREATE":
Límite de contexto: esto + nunca
El siguiente truco a menudo me salva de errores ridículos en medio de la fatiga o el descuido. En el siguiente ejemplo, no daré una evaluación de la calidad del enfoque elegido. Con nosotros, de hecho, esto sucede. Suponga que usa un método en una clase que no tiene acceso a los campos de la clase. Sí, suena aterrador, todo esto es código gubernamental ...
@SomeDecorator({...}) class SomeUiPanel { @Inject private someService: SomeService; public beforeAccessHook() {
En un caso más amplio, pueden ser funciones de devolución de llamada o flecha, que tienen sus propios contextos de ejecución. Y la tarea es: ¿Cómo protegerse de los errores de tiempo de ejecución? Para esto, TypeScript tiene la capacidad de especificar
este contexto.
@SomeDecorator({...}) class SomeUiPanel { @Inject private someService: SomeService; public beforeAccessHook(this: never) {
Para ser justos, diré que nunca vale la pena. En su lugar, puede usar void y {}. Pero es el tipo que nunca llama la atención cuando lees el código.
Las expectativas
Invariantes
Teniendo una idea definitiva de nunca, pensé que el siguiente código debería funcionar:
type Maybe<T> = T | void; function invariant<Cond extends boolean>(condition: Cond, message: string): Cond extends true ? void : never { if (condition) { return; } throw new Error(message); } function f(x: Maybe<number>, c: number) { if (c > 0) { invariant(typeof x === "number", "When c is positive, x should be number"); (x + 1);
Pero por desgracia. La expresión (x + 1) arroja un error: el operador '+' no se puede aplicar a los tipos 'Quizás' y '1'. El ejemplo en sí lo vi en el artículo
Transfiriendo 30,000 líneas de código de Flow a TypeScript.Encuadernación flexible
Pensé que, con la ayuda de Never, puedo controlar los parámetros de la función obligatoria y, bajo ciertas condiciones, deshabilitar los innecesarios. Pero no, eso no funcionará:
function variants<Type extends number | string>(x: Type, c: Type extends number ? number : never): number { if (typeof x === "number") { return x + c; } return +x; } const three = variants(1, 2);
El problema anterior se resuelve de una
manera diferente .
Verificación más estricta
Quería que el compilador ts no se perdiera algo así contrario al sentido común.
variants(<never> {}, <never> {});
Conclusión
Al final, quiero ofrecer una pequeña tarea, de una serie de extrañas rarezas. ¿Qué línea es el error?
class never<never> { never: never; } const whats = new never<string>(); whats.never = "";
OpcionEn el último: el tipo '""' no se puede asignar al tipo 'never'.ts (2322)
Eso es todo lo que quería contar sobre nunca. Gracias a todos por su atención y hasta pronto.