Esse artigo é um complemento ao meu vídeo com o mesmo tema! Veja ele aqui:
Se você está codando em TypeScript, provavelmente já se deparou com um tipo muito estranho, o famoso const as const
, algo assim:
const foo = {
bar: 'string'
} as const
Inclusive, a gente até chegou a falar dele quando falamos sobre enums aqui no blog. Você pode imaginar que, por ter um as
, isso deve ser algum tipo de type casting, ou seja, estamos trocando um tipo pelo outro e forçando o TS a aceitar isso, o que é uma má prática. Mas não!
Este tipo de técnica é chamada de const assertions e, como o nome diz, é uma asserção, ou seja, estamos dando mais informações sobre um tipo para o TypeScript. Mas o que estamos dizendo para ele?
Objetos e arrays
Esse tipo de asserção é geralmente utilizado com objetos e arrays, e também é a forma mais simples de entender o conceito. Mas imagine que você tenha esse objeto:
const args = [1, 2]
const sum = (a, b) => a+b
Tudo que o TS sabe é que args
é um array de números então ele vai tipar como number[]
, o que é aceitável, porque você poderia, por exemplo, fazer um array.push(0)
e ele aceitaria sem problemas, na verdade, na maioria dos casos isso é o que acontece. Mas tem um caso que não.
Existem várias funções no JavaScript que precisam de um número exato de argumentos, um exemplo é a função Math.atan2
que recebe exatamente 2 parâmetros. Outro exemplo é a nossa função de soma. Então esse código não funciona:
const args = [1, 2]
const sum = (a, b) => a+b
sum(...args)
Porque o TS não consegue inferir a quantidade de parâmetros que args tem, e nem o total de itens presentes no array, justamente porque podemos adicionar, remover ou modificar o array da forma que quisermos.
Se a gente quiser dizer que esse array é imutável, uma constante, temos que usar uma const assertion
const args = [1, 2] as const
const sum = (a, b) => a+b
sum(...args)
Neste caso, args
agora é tipado como readonly [1, 2]
, ou seja, não podemos modificar o array, e ele tem exatamente dois elementos, 1 e 2. Portanto podemos passar para a função porque dizemos para o TS que ele sempre vai ter dois elementos.
Vem aprender comigo!
Quer aprender mais sobre criptografia e boas práticas com #TypeScript?
Se inscreva na Formação TS!O mesmo é válido para objetos, se passarmos um objeto qualquer como este:
const obj = {
foo: 1,
bar: 'formacaots.com.br'
}
O TypeScript vai simplesmente tipá-lo como um objeto dessa forma:
const obj: {
foo: number;
bar: string;
}
E podemos adicionar ou remover chaves, além de poder passar qualquer string e qualquer número, ou até mesmo mudar o tipo do objeto, mas se usarmos
const obj = {
foo: 1,
bar: 'formacaots.com.br'
} as const
Nossa tipagem muda para:
const obj: {
readonly foo: 1;
readonly bar: "formacaots.com.br";
}
Veja que agora ele não é só imutável, mas também tem os tipos literais como chaves. E você pode estar se perguntando: "E o Object.freeze? Não faz a mesma coisa? Não é até mais seguro por que está em runtime?"
Existe uma diferença fundamental entre o Object.freeze
e o as const
, enquanto o Object.freeze
vai sim deixar o objeto imutável em tempo de execução, ele só faz isso para o primeiro nível de chaves, então chaves internas e compostas como:
const foo = {
bar: {
baz: 1
}
}
Não funcionam, se você usar Object.freeze(foo)
, isso garante que bar
não pode sofrer a alteração para outro objeto, mas foo.bar.baz
pode ser alterado normalmente. Já o as const
faria o objeto inteiro ser imutável.
Conclusão
const assertions fazem três coisas:
- Em tipos primitivos (string, number, boolean, etc), vai remover a possibilidade de type widening, ou seja, um
'foo'
virarstring
. - Propriedades de objetos se tornam
readonly
- Arrays se tornam tuplas imutáveis (
readonly
)
Você pode inclusive usar as const
em retornos de função, para fazer com que o tipo inferido da função seja sempre inferido como o tipo mais estático possível:
function foo () {
return [1, 2] as const // retorna uma tupla
}