
Recentemente, estudando as causas do mau funcionamento do meu projeto em casa, notei mais uma vez um erro que muitas vezes se repete devido à fadiga. A essência do erro é que, tendo vários identificadores em um bloco de código, quando eu chamo uma função, passo o identificador de um objeto de outro tipo. Neste artigo, falarei sobre como resolver esse problema usando o TypeScript.
Pouco de teoria
O TypeScript é baseado na tipagem estrutural, que se encaixa bem na ideologia do pato do JavaScript. Um número suficiente de artigos foi escrito sobre isso. Não os repetirei, apenas descreverei a principal diferença da digitação nominativa, que é mais comum em outros idiomas. Vamos dar uma olhada em um pequeno exemplo.
class Car { id: number; numberOfWheels: number; move (x: number, y: number) {
Por que o TypeScript se comporta dessa maneira? Esta é apenas uma manifestação da tipagem estrutural. Diferentemente do nominativo, que monitora os nomes dos tipos, a tipagem estrutural decide a compatibilidade dos tipos com base em seu conteúdo. A classe Car contém todas as propriedades e métodos da classe Boat, portanto, Car pode ser usado como um barco. O inverso não é verdadeiro porque o Boat não possui a propriedade numberOfWheels.
Identificadores de digitação
Primeiro, definiremos tipos para identificadores
type CarId: number; type BoatId: number;
e reescreva as classes usando esses tipos.
class Car { id: CarId; numberOfWheels: number; move (x: number, y: number) {
Você notará que a situação não mudou muito, porque ainda não temos controle sobre de onde obtivemos o identificador e você estará certo. Mas este exemplo já oferece algumas vantagens.
Durante o desenvolvimento do programa, o tipo de identificador pode mudar repentinamente. Assim, por exemplo, um determinado número de carro, exclusivo do projeto, pode ser substituído por um número VIN de string. Sem especificar o tipo de identificador, você terá que substituir o número por uma string em todos os locais onde ocorrer. Com a tarefa de tipo, a alteração precisará ser feita apenas em um local onde o próprio tipo é determinado.
Ao chamar funções, obtemos dicas do nosso editor de código, que identificadores de tipo devem ser. Suponha que tenhamos as seguintes funções declaradas:
function getCarById(id: CarId): Car {
Em seguida, obteremos uma dica do editor de que devemos transmitir não apenas um número, mas CarId ou BoatId.
Emule a digitação mais estrita
Não há digitação nominal no TypeScript, mas podemos emular seu comportamento, tornando qualquer tipo exclusivo. Para fazer isso, adicione uma propriedade exclusiva ao tipo. Esse truque é mencionado nos artigos em inglês, sob o termo Branding, e aqui está o que parece:
type BoatId = number & { _type: 'BoatId'}; type CarId = number & { _type: 'CarId'};
Tendo apontado que nossos tipos devem ser um número e um objeto com uma propriedade com um valor único, tornamos nossos tipos incompatíveis no entendimento da tipagem estrutural. Vamos ver como isso funciona.
let carId: CarId; let boatId: BoatId; let car: Car; let boat: Boat; car = getCarById(carId);
Tudo parece bom, exceto pelas últimas quatro linhas. Para criar identificadores, você precisa de uma função auxiliar:
function makeCarIdFromVin(id: number): CarId { return vin as any; }
A desvantagem desse método é que essa função permanecerá em tempo de execução.
Tornando a digitação forte um pouco menos rigorosa
No último exemplo, tive que usar uma função adicional para criar o identificador. Você pode se livrar dele usando a definição da interface Flavor:
interface Flavoring<FlavorT> { _type?: FlavorT; } export type Flavor<T, FlavorT> = T & Flavoring<FlavorT>;
Agora você pode definir tipos para identificadores da seguinte maneira:
type CarId = Flavor<number, “CarId”> type BoatId = Flavor<number, “BoatId”>
Como a propriedade _type é opcional, você pode usar uma conversão implícita:
let boatId: BoatId = 5; // OK let carId: CarId = 3; // OK
E ainda não podemos misturar os identificadores:
let carId: CarId = boatId; // ERROR
Qual opção escolher
Ambas as opções têm o direito de existir. A marca tem a vantagem de proteger uma variável da atribuição direta. Isso é útil se a variável armazena a string em algum formato, como um caminho absoluto do arquivo, data ou endereço IP. A função auxiliar que lida com a conversão de tipos nesse caso também pode verificar e processar dados de entrada. Em outros casos, é mais conveniente usar o Sabor.
Fontes
- Ponto de partida stackoverflow.com
- Interpretação livre do artigo