Narrow Types com `const` modifier

typescript
há 7 meses

Podemos limitar a tipagem de TS para receber tipos mais estreitos ("narrow types"). Por exemplo, como essa variável é uma const, o TS já declara ela do tipo literal ao invés de um tipo abrangente como string

const example = "hello world";
//    ^? const example: "hello world"
TS Playground

Só que quando a gente vai passando valores pra lá e pra cá, alguns desses tipos podem acabar ficando mais abrangentes, o que geralmente não é desejado. Por exemplo, se eu passar para uma função que recebe e retorna string, o tipo mais estreito vai se perder no meio do caminho.

const returnString = (s: string) => s;
const example = returnString('Hello world');
//    ^? const example: string
TS Playground

Com a ajuda de generics, podemos manter o tipo mais estreito

const returnString = <TString extends string>(s: TString) => s;
const example = returnString('Hello world');
//    ^? const example: 'Hello world'
TS Playground

Isso não funciona com tipagens mais complexas. Se eu tentar fazer isso com um array de strings, o array é mutável por padrão, e não tem como TS perceber isso de antemão.

const returnArray = <TArray extends string[]>(arr: TArray) => arr;
const example = returnArray(['alice', 'bob', 'charles']);
//    ^? const example: string[]
TS Playground

Nesses casos, era costume de desenvolvimento colocar um as const depois do valor. Isso anuncia pro TS que esse array não é mutável e ele consegue retornar a tipagem original.

const example = returnArray(['alice', 'bob', 'charles'] as const);
//    ^? const example: ['alice', 'bob', 'charles']
TS Playground

Mas isso não é o suficiente. Quem vai chamar a função precisa lembrar de colocar as const, e isso torna o código refém de erros humanos. Também, já que toda vez que função for executada precisa passa as const, o código fica mais verboso, ainda mais se a função é chamada muitas vezes pelo projeto. Essas falhas ficam bem pronunciadas para bibliotecas, já que o criador da API tem que educar os usuários da biblioteca a usar o as const.

No TS v5.0, chega então o modificador const de tipos genéricos

const returnArray = <const TArray extends string[]>(arr: TArray) => arr;
const example = returnArray(['alice', 'bob', 'charles']);
//    ^? const example: ['alice', 'bob', 'charles']
TS Playground

Com o modificador const antes da declaração do parâmetro TArray, a função usa o valor que é passado para ela como se o argumento utilizasse as const.