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())
}
}
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'.
}
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() }
}
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'.
}
}
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];
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 {
[key: `data-${string}`]: unknown
}
let b: DataOptions = {
"data-foo": true
"qualquer-coisa": true, // Error! 'unknown-property' wasn't declared in 'DataOptions'.
};
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);
}
}
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
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
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.