Digite inferência no TypeScript usando a construção as const e a palavra-chave infer

O TypeScript permite automatizar muitas tarefas que, sem usar essa linguagem, os desenvolvedores precisam resolver de forma independente. Porém, ao trabalhar com TypeScript, não há necessidade de usar constantemente anotações de tipo. O fato é que o compilador faz um ótimo trabalho de inferência de tipo com base no contexto de execução do código. O artigo, cuja tradução publicamos hoje, é dedicado a casos bastante complicados de inferência de tipo nos quais a palavra-chave infere a construção são usadas as const.



Noções básicas de inferência de tipo


Primeiro, dê uma olhada no exemplo mais simples de inferência de tipo.

let variable;

Uma variável que é declarada desta forma é do tipo any. Não fornecemos ao compilador nenhuma dica sobre como o usaremos.

let variable = 'Hello!';

Aqui declaramos uma variável e imediatamente escrevemos um valor nela. O TypeScript agora pode adivinhar que essa variável é do tipo string, agora temos uma variável digitada perfeitamente aceitável.

Uma abordagem semelhante se aplica às funções:

function getRandomInteger(max: number) {
  return Math.floor(Math.random() * max);
}

Nesse código, não indicamos que a função getRandomIntegerretorne um número. Mas o compilador TypeScript sabe disso muito bem.

Inferência de tipo em genéricos


Os conceitos acima estão relacionados a tipos universais (genéricos). Se você quiser saber mais sobre genéricos, dê uma olhada neste e neste material.

Ao criar tipos genéricos, você pode fazer muitas coisas úteis. A inferência de tipos torna o trabalho com tipos universais mais conveniente e simplifica.

function getProperty<ObjectType, KeyType extends keyof ObjectType>(
  object: ObjectType, key: KeyType
) {
  return object[key];
}

Ao usar a função genérica acima, não precisamos especificar explicitamente os tipos.

const dog = {
  name: 'Fluffy'
};
getProperty(dog, 'name');

Essa técnica, entre outras coisas, é muito útil na criação de componentes React universais. Aqui está o material sobre isso.

Usando a palavra-chave inferir


Um dos recursos mais avançados do TypeScript que vem à mente quando se fala em inferência de tipo é a palavra-chave infer.

Considere um exemplo. Crie a seguinte função:

function call<ReturnType>(
  functionToCall: (...args: any[]) => ReturnType, ...args: any[]
): ReturnType {
  return functionToCall(...args);
}

Chame, com a ajuda dessa função, outra função e escreva o que ela retorna em uma constante:

const randomNumber = call(getRandomInteger, 100);

A expressão anterior permite obter o que a função getRandomIntegerque recebeu a entrada retornou como o limite superior do número inteiro aleatório retornado a ela, 100. É verdade que existe um pequeno problema. Está no fato de que nada nos impede de ignorar os tipos de argumentos de função getRandomInteger.

const randomNumber = call(getRandomInteger, '100'); //   

Como o TypeScript suporta parâmetros de espalhamento e repouso em funções de ordem superior, podemos resolver este problema da seguinte maneira:

function call<ArgumentsType extends any[], ReturnType>(
  functionToCall: (...args: ArgumentsType) => ReturnType, ...args: ArgumentsType
): ReturnType {
  return functionToCall(...args);
}

Agora, apontamos que a função callpode processar uma matriz de argumentos de qualquer forma e também que os argumentos devem corresponder às expectativas da função passada a ela.

Agora vamos tentar novamente fazer uma chamada de função incorreta:

const randomNumber = call(getRandomInteger, '100');

Isso resulta em uma mensagem de erro:

Argument of type ‘”100″‘ is not assignable to parameter of type ‘number’.

De fato, seguindo as etapas acima, simplesmente criamos uma tupla. As tuplas no TypeScript são matrizes de comprimento fixo cujos tipos de valor são conhecidos, mas não precisam ser os mesmos.

type Option = [string, boolean];
const option: Option = ['lowercase', true];

Recursos de palavra-chave inferir


Agora vamos imaginar que nosso objetivo não é obter o que a função retorna, mas apenas obter informações sobre o tipo de dados retornado para ela.

type FunctionReturnType<FunctionType extends (...args: any) => ?> = ?;

O tipo acima ainda não está pronto para uso. Precisamos resolver o problema de como determinar o valor de retorno. Aqui você pode descrever tudo manualmente, mas isso vai contra o nosso objetivo.

type FunctionReturnType<ReturnType, FunctionType extends (...args: any) => ReturnType> = ReturnType;
FunctionReturnType<number, typeof getRandomInteger>;

Em vez de fazer isso por conta própria, podemos solicitar ao TypeScript que produza o tipo de retorno. A palavra-chave inferpode ser usada apenas em tipos condicionais. É por isso que nosso código às vezes pode ser um pouco desarrumado.

type FunctionReturnType<FunctionType extends (args: any) => any> = FunctionType extends (...args: any) => infer ReturnType ? ReturnType : any;

Aqui está o que acontece neste código:

  • Diz FunctionTypeexpandir aqui (args: any) => any.
  • Ressaltamos que FunctionReturnTypeesse é um tipo condicional.
  • Verificamos se ele se expande FunctionType (...args: any) => infer ReturnType.

Feito tudo isso, podemos extrair o tipo de retorno de qualquer função.

FunctionReturnType<typeof getRandomInteger>; // number

A tarefa acima é uma tarefa tão comum que o TypeScript possui um utilitário interno ReturnType , desenvolvido para solucionar esse problema.

Construir como const


Outro problema relacionado à inferência de tipo é a diferença entre as palavras-chave conste as letque são usadas ao declarar constantes e variáveis.

let fruit = 'Banana';
const carrot = 'Carrot';

Variável fruit- tem um tipo string. Isso significa que qualquer valor de string pode ser armazenado nele.

E uma constante carroté uma string literal. Pode ser considerado como um exemplo de um subtipo string. A seguinte descrição de literais de cadeia é fornecida neste PR : "O literal de cadeia de caracteres de tipo é um tipo cujo valor esperado é uma cadeia de caracteres com conteúdo de texto equivalente ao mesmo conteúdo da cadeia de caracteres literal."

Esse comportamento pode ser alterado. O TypeScript 3.4 apresenta um novo recurso interessante chamado const assertions que fornece o uso de uma construção as const. Aqui está a aparência do seu uso:

let fruit = 'Banana' as const;

Agora fruité uma string literal. O design as consttambém é conveniente quando alguma entidade precisa ser imutável. Considere o seguinte objeto:

const user = {
  name: 'John',
  role: 'admin'
};

Em JavaScript, a palavra-chave constsignifica que você não pode substituir o que é armazenado em uma constante user. Mas, por outro lado, você pode alterar a estrutura interna de um objeto registrado nesta constante.

Agora, o objeto armazena os seguintes tipos:

const user: {
  name: string,
  role: string
};

Para que o sistema perceba esse objeto como imutável, você pode usar o design as const:

const user = {
  name: 'John',
  role: 'admin'
} as const;

Agora os tipos mudaram. Strings tornaram-se literais de strings, não strings comuns. Mas não só isso mudou. Agora as propriedades são somente leitura:

const user: {
  readonly name: 'John',
  readonly role: 'admin'
};

E ao trabalhar com matrizes, possibilidades ainda mais poderosas se abrem diante de nós:

const list = ['one', 'two', 3, 4];

O tipo dessa matriz é (string | number)[]. Usando essa matriz, as constvocê pode transformá-la em uma tupla:

const list = ['one', 'two', 3, 4] as const;

Agora, o tipo dessa matriz fica assim:

readonly ['one', 'two', 3, 4]

Tudo isso se aplica a estruturas mais complexas. Considere o exemplo que Anders Halesberg deu em seu discurso no TSConf 2019 :

const colors = [
  { color: 'red', code: { rgb: [255, 0, 0], hex: '#FF0000' } },
  { color: 'green', code: { rgb: [0, 255, 0], hex: '#00FF00' } },
  { color: 'blue', code: { rgb: [0, 0, 255], hex: '#0000FF' } },
] as const;

Nossa matriz colorsagora está protegida contra alterações e seus elementos também estão protegidos contra alterações:

const colors: readonly [
    {
        readonly color: 'red';
        readonly code: {
            readonly rgb: readonly [255, 0, 0];
            readonly hex: '#FF0000';
        };
    },
    /// ...
]

Sumário


Neste artigo, vimos alguns exemplos de uso de mecanismos avançados de inferência de tipo no TypeScript. A palavra infer- chave e o mecanismo são usados ​​aqui as const. Essas ferramentas podem ser muito úteis em algumas situações particularmente difíceis. Por exemplo, quando você precisa trabalhar com entidades imutáveis ​​ou quando escreve programas em um estilo funcional. Se você deseja continuar familiarizando-se com este tópico, dê uma olhada neste material.

Queridos leitores! Você usa palavras infer- chave e construção as constno TypeScript?


All Articles