DEV Community

Cover image for O que há de novo no TypeScript 4.4
Lucas Santos for Microsoft Azure

Posted on • Originally published at blog.lsantos.dev on

O que há de novo no TypeScript 4.4

No dia 26 de Agosto de 2021, tivemos o anúncio da versão 4.4 do TypeScript e, como é de costume, vou fazer um highlight de tudo que aconteceu de novo e todas as novidades mais legais do nosso superset favorito!

Análise de fluxo agora com variáveis

Quando usamos TypeScript, uma das grandes falácias que muita gente descreve como sendo um problema que impede o uso, é ter que ficar declarando tipos para todos os dados que você tem. Isso não é verdade.

O compilador do TS é poderoso o suficiente para entender o fluxo de controle e o fluxo do seu código, de forma que ele sabe quando uma variável ou algum outro dado é de um tipo específico de acordo com uma checagem feita anteriormente. Essa checagem é comumente chamada de type guard. E é quando fazemos algo assim:

function foo (bar: unknown) {
  if (typeof bar === 'string') {
    // O TS agora sabe que o tipo é String
    console.log(bar.toUpperCase())
  }
}
Enter fullscreen mode Exit fullscreen mode

Isso é válido não só para casos de unknown mas também para casos onde o tipo é genérico como any.

O grande problema é que, se movermos essa verificação para uma constante ou uma função, o TS se perde no fluxo e não consegue mais entender o que está acontecendo, por exemplo:

function foo (bar: unknown) {
    const isString = typeof bar === 'string'
    if (isString) console.log(arg.toUpperCase())
    // ~~~~~~~~~~~
    // Error! Property 'toUpperCase' does not exist on type 'unknown'.
}
Enter fullscreen mode Exit fullscreen mode

Agora, o TS consegue identificar a constante e o seu retorno, conseguindo prover o resultado sem erros. O mesmo é possível também em tipos complexos, ou tipos discriminantes (discriminant types):

type Animal = 
    | { kind: 'cat', meow: () => void }
    | { kind: 'dog', woof: () => void }

function speak (animal: Animal) {
  const { kind } = animal

  if (kind === 'cat') { animal.meow() }
  else { animal.woof() }
}
Enter fullscreen mode Exit fullscreen mode

Dentro dos tipos extraídos pelo destructuring, agora temos a asserção correta da string. Outra coisa legal é que ele também vai entender de forma transitiva como todos os tipos funcionam, ou seja, ele vai tipo a tipo para poder inferir qual é o tipo atual do objeto a partir das análises que você já fez:

function f(x: string | number | boolean) {
    const isString = typeof x === "string"
    const isNumber = typeof x === "number"
    const isStringOrNumber = isString || isNumber
    if (isStringOrNumber) {
        x // Type of 'x' is 'string | number'.
    }
    else {
        x // Type of 'x' is 'boolean'.
    }
}
Enter fullscreen mode Exit fullscreen mode

Index signatures com Symbols e templates

Existe um tipo chamado de index signature, essencialmente este tipo nos diz que o objeto em questão pode ter chaves de nome arbitrário, como se fosse um dicionário eles são representados como [key: string]: any.

Os únicos tipos possíveis para uma index signature são string e number atualmente, porque são os tipos mais comuns.

Porém, existe um outro tipo chamado Symbol, que é muito utilizado, principalmente por quem constrói libs, para poder indexar os tipos de seus arrays e objetos sem ter de exibí-los ou modificá-los. Com a chegada do 4.4 você agora pode fazer isso:

interface Colors {
    [sym: symbol]: number;
}

const red = Symbol("red");
const green = Symbol("green");
const blue = Symbol("blue");

let colors: Colors = {};

colors[red] = 255;    
let redVal = colors[red];  
Enter fullscreen mode Exit fullscreen mode

Era impossível também ter um subset de string ou de number como os template string types como sendo chaves. Por exemplo, um objeto cujas chaves começam sempre com data-, agora isso é totalmente válido:

interface DataOptions {

}

let b: DataOptions = {
    "data-foo": true
    "qualquer-coisa": true, // Error! 'unknown-property' wasn't declared in 'DataOptions'.
};
Enter fullscreen mode Exit fullscreen mode

Catch agora tem padrão de unknown

Como muitas pessoas sabem (e reclamaram!), quando usamos um try/catch dentro de qualquer função no TypeScript, o bloco catch vai sempre levar um parâmetro error que, por definição, teria um tipo any.

Depois de algumas discussões com a comunidade sobre qual seria o tipo correto, muitas pessoas optaram por ter o tipo unknown como padrão para os erros. Isto porque deixar um tipo aberto como any, essencialmente não dá tipagem nenhuma. Então o TS 4.4 introduz uma nova opção no tsconfig e uma nova flag chamada useUnknownInCatchVariables, que está desativado por padrão para não quebrar a compatibilidade, mas pode e deve ser ativada.

try {
    codigo();
}
catch (err) { // err: unknown

    // Error! Property 'message' does not exist on type 'unknown'.
    console.error(err.message);

    // Define o tipo de erro
    if (err instanceof Error) {
        console.error(err.message);
    }
}
Enter fullscreen mode Exit fullscreen mode

Se você ativar a flag strict, esta flag também será ativada.

Propriedades opcionais exatas

Outro problema trazido pela comunidade era o conflito entre propriedades opcionais declaradas como prop?: <tipo>, pois este tipo de propriedade vai ser expandida para prop: <tipo> | undefined, mas e se a propriedade puder ter mesmo um valor undefined?

Então se alguém quisesse escrever uma propriedade opcional do tipo number, como undefined, isso era ok por padrão, mas causava vários problemas:

interface Pessoa {
  nome: string
  idade?: number
}

const Lucas: Pessoa = { nome: 'Lucas', idade: undefined } // ok
Enter fullscreen mode Exit fullscreen mode

E esta prática ocorre em vários erros porque vamos estar tratando um valor válido com um inexistente. Ainda mais se tivéssemos que lidar com a propriedade idade em algum momento, além disso, cada tipo de método como o Object.assign, Object.keys, for-in, for-of, JSON.stringify e etc, tem tratamentos diferentes para quando uma propriedade existe ou não.

Na versão 4.4 o TS adiciona uma nova flag chamada exactOptionalPropertyTypes, que faz com que este erro desapareça, uma vez que você não poderá usar undefined em uma propriedade tipada como opcional.

interface Pessoa {
  nome: string
  idade?: number
}

const Lucas: Pessoa = { nome: 'Lucas', idade: undefined } // Erro
Enter fullscreen mode Exit fullscreen mode

Assim como a anterior, a propriedade é parte do conjunto strict.

Suporte a blocos estáticos

O ECMA2022 prevê uma nova funcionalidade chamada de static initialization blocks, essa funcionalidade vai permitir que criemos códigos mais complexos de inicialização para membros estáticos de uma classe, vamos falar mais sobre isso aqui no blog em breve!

Mas por enquanto, o TS 4.4 já tem suporte a essa funcionalidade.

Conclusão

Estas foram as alterações mais importantes no TS 4.4, mas não as únicas, tivemos uma série de melhorias de performance e também de leitura e integração com o VSCode.

Top comments (0)