O que há de novo no TypeScript 5.0: declaradores, tipo const, melhoria de enums, velocidade e muito mais!

Publicados: 2023-04-15

O TypeScript 5.0 foi lançado oficialmente em 16 de março de 2023 e agora está disponível para uso de todos. Esta versão apresenta muitos novos recursos com o objetivo de tornar o TypeScript menor, mais simples e mais rápido.

Esta nova versão moderniza os decorators para personalização de classes, permitindo a personalização de classes e seus membros de forma reutilizável. Os desenvolvedores agora podem adicionar um modificador const a uma declaração de parâmetro de tipo, permitindo que inferências do tipo const sejam o padrão. A nova versão também torna todos os enums union enums, simplificando a estrutura do código e acelerando a experiência do TypeScript.

Neste artigo, você explorará as mudanças introduzidas no TypeScript 5.0, fornecendo uma visão aprofundada de seus novos recursos e capacidades.

Introdução ao TypeScript 5.0

TypeScript é um compilador oficial que você pode instalar em seu projeto usando npm. Se você deseja começar a usar o TypeScript 5.0 em seu projeto, pode executar o seguinte comando no diretório do seu projeto:

 npm install -D typescript

Isso instalará o compilador no diretório node_modules , que agora você pode executar com o comando npx tsc .

Você também pode encontrar instruções sobre como usar a versão mais recente do TypeScript no Visual Studio Code nesta documentação.

ICYMI: TypeScript 5.0 está aqui! Explore suas empolgantes atualizações como Declarators, Const Type e Enums aprimorados neste guia Click to Tweet

O que há de novo no TypeScript 5.0?

Neste artigo, vamos explorar as 5 principais atualizações introduzidas no TypeScript. Esses recursos incluem:

Decoradores Modernizados

Os decoradores estão no TypeScript há algum tempo sob uma bandeira experimental, mas a nova versão os atualiza com a proposta do ECMAScript, que agora está no estágio 3, o que significa que está em um estágio em que é adicionado ao TypeScript.

Os decoradores são uma forma de customizar o comportamento das classes e seus membros de forma reutilizável. Por exemplo, se você tiver uma classe com dois métodos, greet e 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();

Em casos de uso do mundo real, essa classe deve ter métodos mais complicados que lidam com alguma lógica assíncrona e têm efeitos colaterais, etc., onde você gostaria de lançar algumas chamadas console.log para ajudar a depurar os 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();

Esse é um padrão que ocorre com frequência e seria conveniente ter uma solução para aplicar a cada método.

É aqui que os decoradores entram em ação. Podemos definir uma função chamada debugMethod que aparece da seguinte forma:

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

No código acima, o debugMethod pega o método original ( originalMethod ) e retorna uma função que faz o seguinte:

  1. Registra uma mensagem “Method Execution Starts.”.
  2. Passa o método original e todos os seus argumentos (incluindo este).
  3. Registra uma mensagem “Method Execution Ends.”.
  4. Retorna qualquer que seja o método original retornado.

Usando decoradores, você pode aplicar o debugMethod aos seus métodos conforme mostrado no código abaixo:

 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();

Isso produzirá o seguinte:

 LOG: Entering method. Hello, my name is Ron. LOG: Exiting method. LOG: Entering method. I am 30 years old. LOG: Exiting method.

Ao definir a função decoradora ( debugMethod ), um segundo parâmetro é passado chamado context (é o objeto context — contém algumas informações úteis sobre como o método decorado foi declarado e também o nome do método). Você pode atualizar seu debugMethod para obter o nome do método do 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; }

Quando você executa seu código, a saída agora carrega o nome de cada método decorado com o decorador debugMethod :

 'greet' Execution Starts. Hello, my name is Ron. 'greet' Execution Ends. 'getAge' Execution Starts. I am 30 years old. 'getAge' Execution Ends.

Há mais coisas que você pode fazer com decoradores. Sinta-se à vontade para verificar a solicitação de recebimento original para obter mais informações sobre como usar decoradores no TypeScript.

Apresentando parâmetros de tipo const

Este é outro grande lançamento que oferece uma nova ferramenta com genéricos para melhorar a inferência que você obtém ao chamar funções. Por padrão, quando você declara valores com const , o TypeScript infere o tipo e não seus valores literais:

 // Inferred type: string[] const names = ['John', 'Jake', 'Jack'];

Até agora, para alcançar a inferência desejada, você tinha que usar a asserção const adicionando “as const”:

 // Inferred type: readonly ["John", "Jake", "Jack"] const names = ['John', 'Jake', 'Jack'] as const;

Quando você chama funções, é semelhante. No código abaixo, o tipo inferido de países é 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'] });

Você pode desejar um tipo mais específico do qual uma maneira de corrigir antes era adicionar a asserção as const :

 // Inferred type: readonly ["USA", "Canada", "India"] const names = getNamesExactly({ countries: ['USA', 'Canada', 'India'] } as const);

Isso pode ser difícil de lembrar e implementar. No entanto, o TypeScript 5.0 apresenta um novo recurso no qual você pode adicionar um modificador const a uma declaração de parâmetro de tipo, que aplicará automaticamente uma inferência semelhante a const como padrão.

 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'] });

O uso de parâmetros de tipo const permite que os desenvolvedores expressem a intenção com mais clareza em seu código. Se uma variável deve ser constante e nunca mudar, usar um parâmetro do tipo const garante que ela nunca seja alterada acidentalmente.

Você pode verificar a solicitação pull original para obter mais informações sobre como o parâmetro de tipo const funciona no TypeScript.

Melhorias em Enums

Enums no TypeScript são uma construção poderosa que permite aos desenvolvedores definir um conjunto de constantes nomeadas. No TypeScript 5.0, foram feitas melhorias nas enumerações para torná-las ainda mais flexíveis e úteis.

Por exemplo, se você tiver o seguinte enum passado para uma função:

 enum Color { Red, Green, Blue, } function getColorName(colorLevel: Color) { return colorLevel; } console.log(getColorName(1));

Antes da introdução do TypeScript 5.0, você poderia passar um número de nível errado e não geraria nenhum erro. Mas com a introdução do TypeScript 5.0, ele gerará um erro imediatamente.

Além disso, a nova versão transforma todos os enums em union enums, criando um tipo exclusivo para cada membro computado. Esse aprimoramento permite o estreitamento de todos os enums e a referência de seus membros 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

Melhorias de desempenho do TypeScript 5.0

O TypeScript 5.0 inclui inúmeras mudanças significativas na estrutura de código, estruturas de dados e extensões algorítmicas. Isso ajudou a melhorar toda a experiência do TypeScript, da instalação à execução, tornando-o mais rápido e eficiente.

Por exemplo, a diferença entre o tamanho do pacote do TypeScript 5.0 e 4.9 é bastante impressionante.

O TypeScript foi migrado recentemente de namespaces para módulos, permitindo que ele aproveite ferramentas de construção modernas que podem realizar otimizações como elevação de escopo. Além disso, a remoção de alguns códigos obsoletos eliminou cerca de 26,4 MB do tamanho do pacote de 63,8 MB do TypeScript 4.9.

Tamanho do pacote TypeScript
Tamanho do pacote TypeScript

Aqui estão algumas vitórias mais interessantes em velocidade e tamanho entre TypeScript 5.0 e 4.9:

Cenário Tempo ou Tamanho Relativo a TS 4.9
tempo de construção do material-ui 90%
Tempo de inicialização do compilador TypeScript 89%
Tempo de construção do dramaturgo 88%
Tempo de autoconstrução do compilador TypeScript 87%
Tempo de compilação do Outlook Web 82%
Tempo de compilação do VS Code 80%
Typescript npm tamanho do pacote 59%

Resolução do empacotador para melhor resolução do módulo

Quando você escreve uma instrução de importação no TypeScript, o compilador precisa saber a que a importação se refere. Ele faz isso usando a resolução do módulo. Por exemplo, quando você escreve import { a } from "moduleA" , o compilador precisa saber a definição de a no moduleA para verificar seu uso.

No TypeScript 4.7, duas novas opções foram adicionadas para as configurações --module e moduleResolution : node16 e nodenext .

O objetivo dessas opções era representar com mais precisão as regras de pesquisa exatas para os módulos ECMAScript no Node.js. No entanto, esse modo possui várias restrições que não são impostas por outras ferramentas.

Por exemplo, em um módulo ECMAScript no Node.js, qualquer importação relativa deve incluir uma extensão de arquivo para que funcione corretamente:

 import * as utils from "./utils"; // Wrong import * as utils from "./utils.mjs"; // Correct

O TypeScript introduziu uma nova estratégia chamada “moduleResolution bundler”. Essa estratégia pode ser implementada adicionando o seguinte código na seção “compilerOptions” do seu arquivo de configuração TypeScript:

 { "compilerOptions": { "target": "esnext", "moduleResolution": "bundler" } }

Essa nova estratégia é adequada para quem usa bundlers modernos, como Vite, esbuild, swc, Webpack, Parcel e outros que utilizam uma estratégia de pesquisa híbrida.

Você pode verificar a solicitação pull original e sua implementação para obter mais informações sobre como o bundler moduleResolution funciona no TypeScript.

depreciações

O TypeScript 5.0 vem com alguma depreciação, incluindo requisitos de tempo de execução, alterações de lib.d.ts e alterações de quebra de API.

  1. Requisitos de tempo de execução: o TypeScript agora tem como alvo o ECMAScript 2018 e o pacote define uma expectativa mínima de mecanismo de 12.20. Portanto, os usuários do Node.js devem ter uma versão mínima de 12.20 ou posterior para usar o TypeScript 5.0.
  2. Mudanças em lib.d.ts: Houve algumas mudanças em como os tipos para o DOM são gerados, o que pode afetar o código existente. Em particular, certas propriedades foram convertidas de números para tipos literais numéricos, e propriedades e métodos para manipulação de eventos recortar, copiar e colar foram movidos entre as interfaces.
  3. Alterações de quebra de API: algumas interfaces desnecessárias foram removidas e algumas melhorias de correção foram feitas. O TypeScript 5.0 também foi movido para módulos.

O TypeScript 5.0 preteriu certas configurações e seus valores correspondentes, incluindo target: ES3 , out , noImplicitUseStrict , keyofStringsOnly , suppressExcessPropertyErrors , suppressImplicitAnyIndexErrors , noStrictGenericChecks , charset , importsNotUsedAsValues ​​e preserveValueImports , bem como preceder em referências de projeto.

Embora essas configurações permaneçam válidas até o TypeScript 5.5, um aviso será emitido para alertar os usuários que ainda as utilizam.

O TypeScript 5.0 é mais simples, rápido e menor! Explore as mudanças que vão revolucionar seu jogo de codificação aqui. Clique para Tweetar

Resumo

Neste artigo, você aprendeu alguns dos principais recursos e melhorias que o TypeScript 5.0 traz, como melhorias em enums, resolução de bundler e parâmetros de tipo const, além de melhorias em velocidade e tamanho.

Se você está pensando em TypeScript para seus próximos projetos, experimente gratuitamente o Application Hosting da Kinsta.

Agora é sua vez! Quais recursos ou melhorias você acha mais atraentes no TypeScript 5.0? Existem alguns significativos que podemos ter esquecido? Deixe-nos saber nos comentários.