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=Excluir  vazio), 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=Extrair  vazio), 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 .

Source link