O TypeScript tem um dos mais poderosos sistemas de tipos de qualquer linguagem de programação-principalmente porque evoluiu para acomodar todas as coisas dinâmicas que você pode fazer em JavaScript.
Incluir recursos como tipos condicionais, de pesquisa e mapeados significa que você pode escrever algumas funções de tipo bastante avançadas no TypeScript.
O que é uma t ype f unção?
O TypeScript permite que você crie um alias de tipo a partir de qualquer tipo existente. Por exemplo:
//Exemplo tipo Str=string; //Uso deixe mensagem: Str='Olá, mundo';
Os apelidos de tipo TypeScript também oferecem suporte a genéricos. Os genéricos são tradicionalmente usados para restringir um tipo com base em outro. Por exemplo, o tipo para um par valor
, setValue
e um genérico é usado para garantir que setValue
seja sempre chamado com o mesmo tipo usado por valor
:
//Exemplo tipo ValueControl={ valor: T, setValue: (newValue: T)=> void, }; //Uso exemplo const: ValueControl ={ valor: 0, setValue: (newValue)=> example.value=newValue, };
Observe que no exemplo acima podemos passar um tipo para ValueControl
. Em nosso exemplo, estamos passando o tipo número
(ou seja, ValueControl
).
O recurso verdadeiramente poderoso do TypeScript é que o tipo passado para a função genérica pode ser usado em condições (com tipos condicionais) e mapeamento (com tipos mapeados). Aqui está um exemplo de um tipo que usa condições para excluir null
e undefined
de um tipo:
/** * Exclua nulo e indefinido de T */ tipo NoEmpty=T estende null | Indefinido ? nunca: T; //Uso tipo StrOrNull=string | nulo; digite Str=NoEmpty ;//corda
No entanto, você não precisa necessariamente usar esses recursos de nível básico, pois o TypeScript também vem com uma série de funções úteis integradas.
Na verdade, nosso tipo NoEmpty
já é enviado como parte do TypeScript (é chamado de NonNullable
e abordamos a seguir). Neste artigo, cobriremos esses tipos de funções com exemplos do mundo real para ver por que você gostaria de usá-los.
Funções de tipo integradas no TypeScript
A partir do TypeScript 4.0, essas são as funções de tipo integradas que você pode usar no TypeScript sem a necessidade de nenhum pacote adicional:
Partial
Parcial
marca todos os membros de um tipo de entrada T
como opcional. Aqui está um exemplo com um tipo simples de Ponto
:
tipo Point={x: número, y: número}; //O mesmo que `{x ?: número, y ?: número}` tipo PartialPoint=Partial;
Um caso de uso comum é o padrão update encontrado em muitas bibliotecas de gerenciamento de estado, onde você fornece apenas um subconjunto das propriedades que deseja alterar. Por exemplo:
classe Estado{ construtor (público atual: T) {} //Só precisa passar as propriedades que você deseja alterar atualização (próximo: Parcial ) { this.current={... this.current,... next}; } } //Uso estado const=novo estado ({x: 0, y: 0}); state.update ({y: 123});//Parcial. Não há necessidade de fornecer `x`. console.log (state.current);//Atualização bem-sucedida: {x: 0, y: 123}
Required
Obrigatório
faz o oposto de Partial
. Isso torna todos os membros de um tipo de entrada T
não opcional . Em outras palavras, isso os torna obrigatórios . Aqui está um exemplo dessa transformação:
digite PartialPoint={x ?: número, y ?: número}; //O mesmo que `{x: número, y: número}` tipo Point=Obrigatório;
Um caso de uso é quando um tipo tem membros opcionais, mas partes do seu código precisam que todos eles sejam fornecidos. Você pode ter uma configuração com membros opcionais, mas internamente, você os inicializa para não ter que lidar com a verificação de nulos em todo o seu código:
//Membros opcionais para consumidores tipo CircleConfig={ cor ?: string, raio ?: número, } class Circle { //Obrigatório: Internamente, todos os membros estarão sempre presentes configuração privada: Obrigatório; construtor (config: CircleConfig) { this.config={ color: config.color ??'verde', radius: config.radius ?? 0, } } desenhar() { //Nenhuma verificação nula necessária:) console.log ( 'Desenhando um círculo.', 'Color:', this.config.color, 'Radius:', this.config.radius ); } }
Readonly
Isso marca todas as propriedades do tipo de entrada T
como somente leitura
. Aqui está um exemplo dessa transformação:
tipo Point={x: número, y: número}; //O mesmo que `{somente leitura x: número, somente leitura y: número}` tipo ReadonlyPoint=Readonly;
Isso é útil para o padrão comum de congelar um objeto para evitar edições. Por exemplo:
função
makeReadonly(objeto: T): Somente leitura { return Object.freeze ({... object}); } const editablePoint={x: 0, y: 0}; editablePoint.x=2;//Sucesso: permitido const readonlyPoint=makeReadonly (editablePoint); readonlyPoint.x=3;//Erro: somente leitura
Escolha
Escolhe apenas as Chaves
especificadas de T
. No código a seguir, temos um Point3D
com as teclas 'x'|'y'|'z'
, e podemos criar um Point2D
escolhendo apenas as chaves 'x'|'y'
:
tipo Point3D={ x: número, y: número, z: número, }; //O mesmo que `{x: número, y: número}` tipo Point2D=Escolha;
Isso é útil para obter um subconjunto de objetos, como vimos no exemplo acima, criando Point2D
.
Um caso de uso mais comum é simplesmente obter as propriedades nas quais você está interessado. Isso é demonstrado abaixo, onde obtemos a largura
e a altura
de todos os CSSProperties
:
//Todas as CSSProperties tipo CSSProperties={ cor ?: string, backgroundColor ?: string, largura ?: número, altura ?: número, //... muito mais }; function setSize ( elemento: HTMLElement, //Uso: só precisa das propriedades de tamanho tamanho: Escolha) { element.setAttribute ('largura', (size.width ?? 0) +'px'); element.setAttribute ('height', (size.height ?? 0) +'px'); }
Registro
Dado um conjunto de nomes de membros especificados por Chaves
, isso cria um tipo em que cada membro é do tipo Valor
. Aqui está um exemplo que demonstra isso:
//O mesmo que `{x: número, y: número}` tipo Ponto=Registro <'x'|'y', número>;
Quando todos os membros de um tipo têm o mesmo Value
, usar Record
pode ajudar a leitura do seu código porque é imediatamente óbvio que todos os membros têm o mesmo Tipo de valor
. Isso é ligeiramente visível no exemplo de Ponto
acima.
Quando há um grande número de membros, Record
é ainda mais útil. Aqui está o código sem usar Record:
tipo PageInfo={ id: string, título: string, }; tipo Pages={ home: PageInfo, serviços: PageInfo, sobre: PageInfo, contato: PageInfo, };
Este é o código usando Record
:
tipo Pages=Registro < 'casa'|'serviços'|'sobre'|'contato', {id: string, título: string} >;
Exclude
Isso exclui tipos Excluded
de T
.
tipo T0=Excluir <'a'|'b'|'c','a'>;//'b'|'c' tipo T1=Excluir <'a'|'b'|'c','a'|'b'>;//'c' tipo T2=Excluirvazio), Função>;//string | número
O caso de uso mais comum é excluir certas chaves de um objeto. Por exemplo:
tipo Dimensions3D='x'|'y'|'z'; tipo Point3D=Registro; //Use exclude para criar Point2D tipo Dimensions2D=Excluir ; tipo Point2D=Registro ;
Você também pode usá-lo para excluir outros membros indesejáveis (por exemplo, null
e undefined
) de uma união:
tipo StrOrNullOrUndefined=string | null | Indefinido; //corda tipo Str=Excluir;
NonNullable
Isso exclui null
e undefined
do tipo T
. Tem o mesmo efeito que Exclude
.
Um rápido JavaScript premier: nullable é algo que pode ser atribuído a um valor nulo ou seja, null
ou undefined
. Portanto, não anulável é algo que não deve aceitar valores nulos.
Aqui está o mesmo exemplo que vimos com Exclude
, mas desta vez, vamos usar NonNullable
:
tipo StrOrNullOrUndefined=string | null | Indefinido; //Igual a `string` //O mesmo que `Exclude` tipo Str=NonNullable ;
Extrair
Isso extrai os tipos extraídos
de T
. Você pode vê-lo como o oposto de Exclude
porque, em vez de especificar quais tipos deseja excluir ( Exclude
), você especifica quais tipos deseja manter/extrair ( Extrair
):
tipo T0=Extrair <'a'|'b'|'c','a'>;//'uma' tipo T1=Extrair <'a'|'b'|'c','a'|'b'>;//'a'|'b' tipo T2=Extrairvazio), Função>;//()=> void
O extrato pode ser considerado uma interseção de dois tipos fornecidos. Isso é demonstrado abaixo, onde os elementos comuns 'a'|'b'
são extraídos:
tipo T3=Extrair <'a'|'b'|'c','a'|'b'|'d'>;//'a'|'b'
Um caso de uso de Extract
é encontrar a base comum de dois tipos, assim:
tipo Person={ id: string, nome: string, email: string, }; tipo Animal={ id: string, nome: string, espécie: corda, }; /** Use Extrair para obter as chaves comuns */ tipo CommonKeys=Extrair; /** * Mapeie as chaves para encontrar a estrutura comum * O mesmo que `{id: string, name: string}` **/ tipo Base={ [K em CommonKeys]: (Animal e Pessoa) [K] };
Omitir
Omite as chaves especificadas por Chaves
do tipo T
. Aqui está um exemplo:
tipo Point3D={ x: número, y: número, z: número, }; //O mesmo que `{x: número, y: número}` digite Point2D=Omitir;
Omitir certas propriedades de um objeto antes de passá-lo é um padrão comum em JavaScript.
A função do tipo Omit
oferece uma maneira conveniente de anotar essas transformações. É, por exemplo, convencional remover PII (informações de identificação pessoal, como endereços de e-mail e nomes) antes de fazer o login. Você pode anotar essa transformação com Omit
.
tipo Person={ id: string, hasConsent: boolean, nome: string, email: string, }; //Utilitário para remover PII de `Pessoa` function cleanPerson (pessoa: Pessoa): Omitir{ const {nome, e-mail,... limpar}=pessoa; retornar limpo; }
Parameters
Dado um tipo de Função
, este tipo retorna os tipos dos parâmetros da função como uma tupla. Aqui está um exemplo que demonstra essa transformação:
função
add (a: número, b: número) { retornar a + b; } //O mesmo que `[a: número, b: número]` digite AddParameters=Parâmetros;
Você pode combinar Parâmetros
com tipos de pesquisa de índice do TypeScript para obter qualquer parâmetro individual. Podemos até buscar o tipo do primeiro parâmetro:
função
add (a: número, b: número) { retornar a + b; } //O mesmo que `número` tipo A=Parâmetros[0];
Um caso de uso importante para Parâmetros
é a capacidade de capturar o tipo de um parâmetro de função para que você possa usá-lo em seu código para garantir a segurança do tipo.
//Uma função de salvar em uma biblioteca externa função salvar (pessoa: {id: string, nome: string, email: string}) { console.log ('Salvando', pessoa); } //Certifique-se de que ryan corresponde ao que é esperado por `save` const ryan: Parâmetros[0]={ id:'1337', nome:'Ryan', email:'[email protected]', };
ConstructorParameters
Isso é semelhante ao tipo Parâmetros
que vimos acima. A única diferença é que ConstructorParameters
funciona em um construtor de classe, assim:
class Point { x privado: número; privado y: número; construtor (inicial: {x: número, y: número}) { this.x=initial.x; this.y=initial.y; } } //Igual a `[inicial: {x: número, y: número}]` tipo PointParameters=ConstructorParameters;
E, claro, o principal caso de uso para ConstructorParamters
também é semelhante. No exemplo a seguir, nós o usamos para garantir que nossos valores iniciais sejam algo que serão aceitos pela classe Point
:
class Point { x privado: número; privado y: número; construtor (inicial: {x: número, y: número}) { this.x=initial.x; this.y=initial.y; } } //Certifique-se de que `center` corresponde ao que é esperado pelo construtor` Point` const center: ConstructorParameters[0]={ x: 0, y: 0, };
ReturnType
Dado um tipo de Função
, obtém o tipo retornado pela função.
function createUser (name: string) { Retorna { id: Math.random (), nome nome }; } //Igual a `{id: número, nome: string}` tipo User=ReturnType;
Um possível caso de uso é semelhante ao que vimos com Parâmetros
. Ele permite que você obtenha o tipo de retorno de uma função para que você possa usá-lo para digitar outras variáveis. Na verdade, isso é demonstrado no exemplo acima.
Você também pode usar ReturnType
para garantir que a saída de uma função seja a mesma que a entrada de outra função. Isso é comum no React, onde você tem um gancho personalizado que gerencia o estado necessário para um componente React.
import React from'react'; //gancho personalizado function useUser () { const [nome, setName]=React.useState (''); const [email, setEmail]=React.useState (''); Retorna { nome, nome do conjunto, o email, setEmail, }; } //O componente personalizado usa o valor de retorno do gancho function User (props: ReturnType) { Retorna ( <> Nome: {props.name}Email: {props.email}> ); }
InstanceType
InstanceType
é semelhante ao ReturnType
que vimos acima. A única diferença é que InstanceType
funciona em um construtor de classe.
class Point { x: número; y: número; construtor (inicial: {x: número, y: número}) { this.x=initial.x; this.y=initial.y; } } //O mesmo que `{x: número, y: número}` digite PointInstance=InstanceType;
Você normalmente não precisa usar isso para qualquer classe estática como a classe Point
acima. Isso ocorre porque você pode apenas usar a anotação de tipo Ponto
, conforme mostrado aqui:
class Point { x: número; y: número; construtor (inicial: {x: número, y: número}) { this.x=initial.x; this.y=initial.y; } } //Você não faria isso const verbose: InstanceType=new Point ({x: 0, y: 0}); //Porque você pode fazer isso const simples: Ponto=novo Ponto ({x: 0, y: 0});
No entanto, o TypeScript também permite que você crie classes dinâmicas, por exemplo, a seguinte função DisposibleMixin
retorna uma classe em tempo real:
type Class=new (... args: any [])=> any; //cria uma classe dinamicamente e a retorna function DisposableMixin(base: Base) { return class extends base { isDisposed: boolean=false; dispose () { this.isDisposed=true; } }; }
Agora podemos usar InstanceType
para obter o tipo de instâncias criadas invocando DisposiblePoint
:
type Class=new (... args: any [])=> any; function DisposableMixin(base: Base) { return class extends base { isDisposed: boolean=false; dispose () { this.isDisposed=true; } }; } //classe criada dinamicamente const DisposiblePoint=DisposableMixin (classe { x=0; y=0; }); //O mesmo que `{isDisposed, dispose, x, y}` deixe um exemplo: InstanceType ;
Conclusão
Como vimos, existem muitos tipos de utilitários integrados que vêm com o TypeScript. Muitas dessas são definições simples que você mesmo pode escrever, por exemplo, se quiser excluir nulo e indefinido, você pode facilmente escrever o seguinte:
//Sua criação personalizada tipo NoEmpty=T estende null | Indefinido ? nunca: T; //Uso tipo StrOrNull=string | nulo; //string-As pessoas precisam olhar `NoEmpty` para entender o que significa digite Str=NoEmpty ;
No entanto, usar a versão integrada NonNullable
(que faz a mesma coisa) pode melhorar a legibilidade em seu código, de forma que pessoas familiarizadas com a biblioteca padrão do TypeScript não precisem analisar o corpo T extends null | Indefinido ? nunca: T;
para entender o que está acontecendo.
Isso é demonstrado abaixo:
//Não há necessidade de criar algo personalizado //Uso tipo StrOrNull=string | nulo; //string-Pessoas que conhecem TS sabem o que `NonNullable` faz tipo Str=NonNullable;
Por fim, você deve usar os tipos embutidos como ReadOnly
/ Parcial
/ Obrigatório
sempre que possível, em vez de criar os personalizados. Além de economizar tempo ao escrever código, também evita que você tenha que pensar em nomear seus utilitários, já que eles foram nomeados para você pela equipe do TypeScript.
A postagem Usando tipos de utilitários integrados no TypeScript apareceu primeiro no LogRocket Blog .