Tudo sobre o novo operador satisfies no TypeScript

Lucas Santos - Oct 21 '22 - - Dev Community

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
}
Enter fullscreen mode Exit fullscreen mode

A inferência automática do tipo do TS vai ser que palette é:

{
  red: [255, 0, 0],
  green: "#00ff00",
  bleu: [0, 0, 255]
}
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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]
}
Enter fullscreen mode Exit fullscreen mode

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]
}
Enter fullscreen mode Exit fullscreen mode

Vamos ter um tipo como esse:

{
  red: string | RGB,
  green: string | RGB,
  bleu: string | RGB
}
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Agora o novo tipo de palette vai ser:

{
    red: [number, number, number];
    green: string;
    blue: [number, number, number];
}
Enter fullscreen mode Exit fullscreen mode

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.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .