No último beta aberto do TypeScript, os devs mostraram o que virá na versão 4.9 da linguagem. Além de algumas otimizações, que são bastante comuns, na inferência de tipos, vamos ter um novo operador na linguagem, o satisfies
. Bora entender um pouco mais como esse operador vai funcionar.
O satisfies
Quando estamos desenvolvendo com TS, costumamos cair em um dilema complicado. A inferência de tipos do TS é muito boa e específica, então é legal termos essa inferência super específica, mas ao mesmo tempo, essa inferência não leva em consideração alguns fatores, como o nome de chaves.
No exemplo que tiramos do próprio anúncio do beta, temos uma ideia muito boa do que isso significa, vamos imaginar o seguinte, temos um tipo que pode ser ou uma string ou uma tupla RGB, ou seja, temos que ter um [number, number, number]
, podemos fazer dessa forma:
const palette = {
red: [255, 0, 0],
green: "#00ff00",
bleu: [0, 0, 255]
// ^^^ temos um typo aqui
}
A inferência automática do tipo do TS vai ser que palette
é:
{
red: [255, 0, 0],
green: "#00ff00",
bleu: [0, 0, 255]
}
Ou seja, temos um tipo literal. Esses tipos permitem que usemos determinadas funções para cada tipo de dados em certos casos, por exemplo, uma função toUpperCase
em green
já que é uma string, e um .at(0)
no red
que é um array:
// Conseguimos usar métodos de array aqui
const redComponent = palette.red.at(0);
// Mas não aqui, porque só podemos usar strings
const greenNormalized = palette.green.toUpperCase();
Só que perdemos a inferência do nome das chaves, veja que temos um typo, deveríamos ter escrito blue
e não bleu
, mas para corrigir isso teríamos que criar um novo tipo mais genérico e dizer para o TS que aquele tipo é o tipo do objeto, nesse caso o tipo poderia ser:
type Colors = "red" | "green" | "blue"
type RGB = [red: number, green: number, blue: number]
const palette: Record<Colors, string | RGB> = {
red: [255, 0, 0],
green: "#00ff00",
bleu: [0, 0, 255]
}
Isso vai nos dar o erro Object literal may only specify known properties, and 'bleu' does not exist in type 'Record<Colors, string | RGB>'.
, que é esperado, ou seja, estamos batendo as chaves com o objeto, então podemos corrigi-lo:
const palette: Record<Colors, string | RGB> = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255]
}
Vamos ter um tipo como esse:
{
red: string | RGB,
green: string | RGB,
bleu: string | RGB
}
Mas agora a gente vai ter dois erros bem chatos nas duas funções de baixo:
// Property 'at' does not exist on type 'string | RGB'.
const redComponent = palette.red.at(0);
// Property 'toUpperCase' does not exist on type 'string | RGB'.
const greenNormalized = palette.green.toUpperCase();
O primeiro porque não temos .at
em strings e o segundo porque não temos .toUpperCase
em RGB
, e isso só acontece porque, quando dizemos para o TS que aquele objeto é de um determinado tipo, esse tipo vai generalizar o que vai entrar nesse objeto, por exemplo, nesse caso ele está em dúvida se cada chave é uma string ou array de números, e ai não sabemos inferir as duas coisas.
Para resolver esse problema teríamos que fazer uma conversão explícita:
nent = (palette.red as RGB).at(0);
const greenNormalized = (palette.green as string).toUpperCase();
E é para isso que o satisfies
existe. Poderíamos manter a especificação mais detalhada, enquanto dizemos que ela deve seguir algum tipo de molde. Por exemplo:
const palette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255]
} satisfies Record<Colors, string | RGB>
Agora o novo tipo de palette
vai ser:
{
red: [number, number, number];
green: string;
blue: [number, number, number];
}
Veja como agora estamos usando o poder da inferência específica com o poder da inferência de tipos declarada para poder tirar o máximo proveito do TypeScript. Dessa forma vamos poder usar funções que são string dentro de chaves que são string e funções de arrays dentro de chaves numéricas.
Conclusão
Essa é, sem dúvida uma das melhores inclusões do superset nos últimos tempos. Acredito que a principal recomendação a partir de agora seja escrever todos os seus tipos sem nenhum tipo de asserção manual, e utilizar o satisfies para poder dizer a qual objeto ele deve pertencer, dessa forma conseguimos manter o melhor de todos os mundos.
Para mais exemplos, dê uma olhada na issue que propôs essa funcionalidade.