Novedades de TypeScript 5.0: declaradores, tipo constante, mejora de enumeraciones, velocidad y mucho más.
Publicado: 2023-04-15TypeScript 5.0 se lanzó oficialmente el 16 de marzo de 2023 y ahora está disponible para que todos lo usen. Esta versión presenta muchas características nuevas con el objetivo de hacer que TypeScript sea más pequeño, simple y rápido.
Esta nueva versión moderniza los decoradores para la personalización de clases, lo que permite la personalización de clases y sus miembros de forma reutilizable. Los desarrolladores ahora pueden agregar un modificador const a una declaración de parámetro de tipo, lo que permite que las inferencias similares a const sean las predeterminadas. La nueva versión también hace que todas las enumeraciones sean unidas, lo que simplifica la estructura del código y acelera la experiencia de TypeScript.
En este artículo, explorará los cambios introducidos en TypeScript 5.0, brindando una visión detallada de sus nuevas características y capacidades.
Primeros pasos con TypeScript 5.0
TypeScript es un compilador oficial que puede instalar en su proyecto usando npm. Si desea comenzar a usar TypeScript 5.0 en su proyecto, puede ejecutar el siguiente comando en el directorio de su proyecto:
npm install -D typescript
Esto instalará el compilador en el directorio node_modules , que ahora puede ejecutar con el comando npx tsc
.
También puede encontrar instrucciones sobre el uso de la versión más reciente de TypeScript en Visual Studio Code en esta documentación.
¿Qué hay de nuevo en TypeScript 5.0?
En este artículo, exploremos 5 actualizaciones principales introducidas en TypeScript. Estas características incluyen:
Decoradores Modernizados
Los decoradores han estado en TypeScript por un tiempo bajo una bandera experimental, pero la nueva versión los pone al día con la propuesta de ECMAScript, que ahora se encuentra en la etapa 3, lo que significa que está en una etapa en la que se agrega a TypeScript.
Los decoradores son una forma de personalizar el comportamiento de las clases y sus miembros de forma reutilizable. Por ejemplo, si tiene una clase que tiene dos métodos, greet
y getAge
:
class Person { name: string; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } greet() { console.log(`Hello, my name is ${this.name}.`); } getAge() { console.log(`I am ${this.age} years old.`); } } const p = new Person('Ron', 30); p.greet(); p.getAge();
En los casos de uso del mundo real, esta clase debería tener métodos más complicados que manejen cierta lógica asíncrona y tengan efectos secundarios, etc., en los que querrá incluir algunas llamadas console.log
para ayudar a depurar los métodos.
class Person { name: string; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } greet() { console.log('LOG: Method Execution Starts.'); console.log(`Hello, my name is ${this.name}.`); console.log('LOG: Method Execution Ends.'); } getAge() { console.log('LOG: Method Execution Starts.'); console.log(`I am ${this.age} years old.`); console.log('Method Execution Ends.'); } } const p = new Person('Ron', 30); p.greet(); p.getAge();
Este es un patrón que ocurre con frecuencia, y sería conveniente tener una solución para aplicar a cada método.
Aquí es donde entran en juego los decoradores. Podemos definir una función llamada debugMethod
que aparece de la siguiente manera:
function debugMethod(originalMethod: any, context: any) { function replacementMethod(this: any, ...args: any[]) { console.log('Method Execution Starts.'); const result = originalMethod.call(this, ...args); console.log('Method Execution Ends.'); return result; } return replacementMethod; }
En el código anterior, debugMethod
toma el método original ( originalMethod
) y devuelve una función que hace lo siguiente:
- Registra un mensaje "Comienza la ejecución del método".
- Pasa el método original y todos sus argumentos (incluido este).
- Registra un mensaje "Termina la ejecución del método".
- Devuelve lo que devolvió el método original.
Al usar decoradores, puede aplicar debugMethod
a sus métodos como se muestra en el siguiente código:
class Person { name: string; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } @debugMethod greet() { console.log(`Hello, my name is ${this.name}.`); } @debugMethod getAge() { console.log(`I am ${this.age} years old.`); } } const p = new Person('Ron', 30); p.greet(); p.getAge();
Esto generará lo siguiente:
LOG: Entering method. Hello, my name is Ron. LOG: Exiting method. LOG: Entering method. I am 30 years old. LOG: Exiting method.
Al definir la función decoradora ( debugMethod
), se pasa un segundo parámetro llamado context
(es el objeto de contexto; tiene información útil sobre cómo se declaró el método decorado y también el nombre del método). Puede actualizar su debugMethod
para obtener el nombre del método del objeto context
:
function debugMethod( originalMethod: any, context: ClassMethodDecoratorContext ) { const methodName = String(context.name); function replacementMethod(this: any, ...args: any[]) { console.log(`'${methodName}' Execution Starts.`); const result = originalMethod.call(this, ...args); console.log(`'${methodName}' Execution Ends.`); return result; } return replacementMethod; }
Cuando ejecute su código, la salida ahora llevará el nombre de cada método que está decorado con el decorador debugMethod
:
'greet' Execution Starts. Hello, my name is Ron. 'greet' Execution Ends. 'getAge' Execution Starts. I am 30 years old. 'getAge' Execution Ends.
Hay más de lo que puede hacer con los decoradores. No dude en consultar la solicitud de extracción original para obtener más información sobre cómo usar decoradores en TypeScript.
Introducción a los parámetros de tipo const
Este es otro gran lanzamiento que le brinda una nueva herramienta con genéricos para mejorar la inferencia que obtiene cuando llama a funciones. De forma predeterminada, cuando declara valores con const
, TypeScript infiere el tipo y no sus valores literales:
// Inferred type: string[] const names = ['John', 'Jake', 'Jack'];
Hasta ahora, para lograr la inferencia deseada, había que usar la aserción const agregando “as const”:
// Inferred type: readonly ["John", "Jake", "Jack"] const names = ['John', 'Jake', 'Jack'] as const;
Cuando llamas a funciones, es similar. En el siguiente código, el tipo de países inferido es string[]
:
type HasCountries = { countries: readonly string[] }; function getCountriesExactly(arg: T): T['countries'] { return arg.countries; } // Inferred type: string[] const countries = getCountriesExactly({ countries: ['USA', 'Canada', 'India'] });
Es posible que desee un tipo más específico del cual una forma de corregir antes ahora ha sido agregar la aserción as const
:
// Inferred type: readonly ["USA", "Canada", "India"] const names = getNamesExactly({ countries: ['USA', 'Canada', 'India'] } as const);
Esto puede ser difícil de recordar e implementar. Sin embargo, TypeScript 5.0 presenta una nueva función en la que puede agregar un modificador const a una declaración de parámetro de tipo, que aplicará automáticamente una inferencia similar a const como predeterminada.
type HasCountries = { countries: readonly string[] }; function getNamesExactly(arg: T): T['countries'] { return arg.countries; } // Inferred type: readonly ["USA", "Canada", "India"] const names = getNamesExactly({ countries: ['USA', 'Canada', 'India'] });
El uso de parámetros de tipo const
permite a los desarrolladores expresar la intención más claramente en su código. Si se pretende que una variable sea constante y nunca cambie, el uso de un parámetro de tipo const
garantiza que nunca se pueda cambiar accidentalmente.
Puede consultar la solicitud de extracción original para obtener más información sobre cómo funciona el parámetro de tipo const en TypeScript.
Mejoras a las enumeraciones
Las enumeraciones en TypeScript son una construcción poderosa que permite a los desarrolladores definir un conjunto de constantes con nombre. En TypeScript 5.0, se han realizado mejoras en las enumeraciones para que sean aún más flexibles y útiles.
Por ejemplo, si tiene la siguiente enumeración pasada a una función:
enum Color { Red, Green, Blue, } function getColorName(colorLevel: Color) { return colorLevel; } console.log(getColorName(1));
Antes de la introducción de TypeScript 5.0, podía pasar un número de nivel incorrecto y no arrojaba ningún error. Pero con la introducción de TypeScript 5.0, arrojará un error de inmediato.
Además, la nueva versión convierte todas las enumeraciones en enumeraciones de unión al crear un tipo único para cada miembro calculado. Esta mejora permite reducir todas las enumeraciones y hacer referencia a sus miembros como tipos:
enum Color { Red, Purple, Orange, Green, Blue, Black, White, } type PrimaryColor = Color.Red | Color.Green | Color.Blue; function isPrimaryColor(c: Color): c is PrimaryColor { return c === Color.Red || c === Color.Green || c === Color.Blue; } console.log(isPrimaryColor(Color.White)); // Outputs: false console.log(isPrimaryColor(Color.Red)); // Outputs: true
Mejoras de rendimiento de TypeScript 5.0
TypeScript 5.0 incluye numerosos cambios significativos en la estructura del código, las estructuras de datos y las extensiones algorítmicas. Esto ha ayudado a mejorar toda la experiencia de TypeScript, desde la instalación hasta la ejecución, haciéndola más rápida y eficiente.
Por ejemplo, la diferencia entre el tamaño del paquete de TypeScript 5.0 y 4.9 es bastante impresionante.
TypeScript se migró recientemente de los espacios de nombres a los módulos, lo que le permite aprovechar las herramientas de compilación modernas que pueden realizar optimizaciones como la elevación del alcance. Además, la eliminación de parte del código obsoleto ha reducido aproximadamente 26,4 MB del tamaño del paquete de 63,8 MB de TypeScript 4.9.
Aquí hay algunas ganancias más interesantes en velocidad y tamaño entre TypeScript 5.0 y 4.9:
Guión | Tiempo o tamaño relativo a TS 4.9 |
tiempo de compilación de la interfaz de usuario del material | 90% |
Tiempo de inicio del compilador de TypeScript | 89% |
Tiempo de construcción del dramaturgo | 88% |
Tiempo de autoconstrucción del compilador de TypeScript | 87% |
Tiempo de compilación web de Outlook | 82% |
Tiempo de compilación del código VS | 80% |
tamaño del paquete mecanografiado npm | 59% |
Resolución de paquete para una mejor resolución del módulo
Cuando escribe una declaración de importación en TypeScript, el compilador necesita saber a qué se refiere la importación. Lo hace usando la resolución del módulo. Por ejemplo, cuando escribe import { a } from "moduleA"
, el compilador necesita conocer la definición de a
en moduleA
para verificar su uso.
En TypeScript 4.7, se agregaron dos nuevas opciones para la configuración --module
y moduleResolution
: node16
y nodenext
.
El propósito de estas opciones era representar con mayor precisión las reglas de búsqueda exactas para los módulos ECMAScript en Node.js. Sin embargo, este modo tiene varias restricciones que otras herramientas no aplican.
Por ejemplo, en un módulo ECMAScript en Node.js, cualquier importación relativa debe incluir una extensión de archivo para que funcione correctamente:
import * as utils from "./utils"; // Wrong import * as utils from "./utils.mjs"; // Correct
TypeScript ha introducido una nueva estrategia llamada "moduleResolution bundler". Esta estrategia se puede implementar agregando el siguiente código en la sección "compilerOptions" de su archivo de configuración de TypeScript:
{ "compilerOptions": { "target": "esnext", "moduleResolution": "bundler" } }
Esta nueva estrategia es adecuada para aquellos que usan paquetes modernos como Vite, esbuild, swc, Webpack, Parcel y otros que utilizan una estrategia de búsqueda híbrida.
Puede consultar la solicitud de extracción original y su implementación para obtener más información sobre cómo funciona el paquete moduleResolution
en TypeScript.
Deprecaciones
TypeScript 5.0 viene con cierta depreciación, incluidos los requisitos de tiempo de ejecución, los cambios de lib.d.ts y los cambios importantes de API.
- Requisitos de tiempo de ejecución: TypeScript ahora apunta a ECMAScript 2018 y el paquete establece una expectativa mínima de motor de 12.20. Por lo tanto, los usuarios de Node.js deben tener una versión mínima de 12.20 o posterior para usar TypeScript 5.0.
- Cambios en lib.d.ts: Ha habido algunos cambios en la forma en que se generan los tipos para el DOM, lo que puede afectar el código existente. En particular, ciertas propiedades se han convertido de números a tipos literales numéricos, y las propiedades y los métodos para el manejo de eventos de cortar, copiar y pegar se han movido a través de las interfaces.
- Cambios importantes en la API: se eliminaron algunas interfaces innecesarias y se realizaron algunas mejoras de corrección. TypeScript 5.0 también se ha movido a módulos.
TypeScript 5.0 ha desaprobado ciertas configuraciones y sus valores correspondientes, incluidos target: ES3
, out
, noImplicitUseStrict
, keyofStringsOnly
, suppressExcessPropertyErrors
, suppressImplicitAnyIndexErrors
, noStrictGenericChecks
, charset
, importsNotUsedAsValues
y preserveValueImports
, así como anteponer en las referencias del proyecto.
Si bien estas configuraciones seguirán siendo válidas hasta TypeScript 5.5, se emitirá una advertencia para alertar a los usuarios que aún las usan.
Resumen
En este artículo, ha aprendido algunas de las principales funciones y mejoras que trae TypeScript 5.0, como las mejoras en las enumeraciones, la resolución del paquete y los parámetros de tipo const, junto con las mejoras en la velocidad y el tamaño.
Si está pensando en TypeScript para sus próximos proyectos, pruebe el alojamiento de aplicaciones de Kinsta de forma gratuita.
¡Ahora es tu turno! ¿Qué funciones o mejoras encuentra más atractivas en TypeScript 5.0? ¿Hay alguno significativo que podamos haber pasado por alto? Háganos saber en los comentarios.