Os 5 mandamentos do desenvolvedor TypeScript

imagem


Mais e mais projetos e equipes estão usando o TypeScript. No entanto, aplicar o TypeScript e extrair o máximo dele é algo muito diferente.


Apresento a você uma lista de práticas recomendadas de alto nível para o uso do TypeScript que o ajudarão a tirar o máximo proveito dessa linguagem.


Não minta


Tipos é um contrato. O que isso significa? Quando você implementa uma função, seu tipo se torna uma promessa para outros desenvolvedores (ou para você mesmo no futuro!) Que, quando chamada, essa função retornará um certo tipo de valor.


No exemplo a seguir, o tipo da função getUser garante que ele retorne um objeto que sempre tenha duas propriedades: name e age .


 interface User { name: string; age: number; } function getUser(id: number): User { /* ... */ } 

TypeScript é uma linguagem muito flexível. Possui muitos compromissos projetados para facilitar a introdução do idioma. Por exemplo, permite implementar a função getUser como esta:


 function getUser(id: number): User { return { age: 12 } as User; } 

Não faça isso! Isso é falso. Ao criar esse código, você mente para outros desenvolvedores (que usarão sua função em suas funções). Eles esperam que o objeto retornado por getUser sempre tenha algum tipo de campo de name . Mas ele não está lá! Além disso, o que acontece quando seu colega escreve getUser(1).name.toString() ? Você sabe muito bem que ...


Aqui, é claro, a mentira parece óbvia. No entanto, trabalhando com uma grande base de código, muitas vezes você se encontra em situações em que o valor que deseja retornar (ou passar) quase corresponde ao tipo esperado. Leva tempo e esforço para encontrar a causa da incompatibilidade de tipos e você está com pressa ... então decide usar a conversão de tipos.


No entanto, ao fazer isso, você está quebrando o contrato sagrado . É SEMPRE melhor reservar um tempo e entender por que os tipos não coincidem do que usar a conversão de tipos. É muito provável que algum bug de tempo de execução esteja oculto sob a superfície.


Não minta. Obedeça aos seus contratos.


Seja preciso


Tipos são documentação. Ao documentar uma função, você não deseja transmitir o máximo de informação possível?


 //   function getUser(id) { /* ... */ } //     : name  age function getUser(id) { /* ... */ } //  id       id , //     : name  age. //     undefined. function getUser(id) { /* ... */ } 

Qual comentário da função getUser você gostaria mais? Quanto mais você souber que uma função retorna, melhor. Por exemplo, sabendo que ele pode retornar undefined , você pode escrever um bloco if para verificar se o objeto que a função retornou está definido antes de solicitar as propriedades desse objeto.


Exatamente o mesmo com os tipos: quanto mais precisamente um tipo é descrito, mais informações ele transmite.


 function getUserType(id: number): string { /* ... */ } function getUserType(id: number): 'standard' | 'premium' | 'admin' { /* ... */ } 

A segunda versão da função getUserType muito mais informativa e, portanto, o chamador está em uma situação muito mais conveniente. É mais fácil processar o valor se você provavelmente sabe (contratos, lembra?) Que será uma das três linhas especificadas, e não apenas uma linha. Para começar com o que você tem certeza - um valor não pode ser uma sequência vazia.


Vamos considerar um exemplo mais real. O tipo State descreve o estado do componente que solicita alguns dados do back-end. Esse tipo é preciso?


 interface State { isLoading: boolean; data?: string[]; errorMessage?: string; } 

Um cliente que usa esse tipo deve manipular uma combinação improvável de valores de propriedade do estado. Por exemplo, uma situação é impossível quando as propriedades data e errorMessage são definidas simultaneamente: uma solicitação de dados pode ter êxito ou falhar.


Podemos tornar o tipo muito mais preciso com a ajuda de tipos de união discriminados :


 type State = | { status: 'loading' } | { status: 'successful', data: string[] } | { status: 'failed', errorMessage: string }; 

Agora, o cliente que usa esse tipo tem muito mais informações: ele não precisa mais processar combinações incorretas de propriedades.


Seja preciso. Passe o máximo de informações possível sobre seus tipos.


Comece com tipos


Como os tipos são contrato e documentação, eles são ótimos para projetar suas funções (ou métodos).


Existem muitos artigos na Internet que aconselham os programadores a pensar antes de escrever código . Eu compartilho totalmente essa abordagem. A tentação de pular diretamente para o código é grande, mas isso geralmente leva a más decisões. Um pouco de tempo gasto pensando na implementação sempre compensa generosamente.


Os tipos são extremamente úteis nesse processo. Pensar leva à criação de assinaturas de tipos de funções relacionadas à solução do seu problema. E isso é ótimo, porque você se concentra no que suas funções fazem, em vez de pensar em como elas o fazem.


O React JS tem o conceito de um Higher Order Components (HOC). Essas são funções que estendem o componente especificado de alguma forma. Por exemplo, você pode criar um componente de ordem superior com o withLoadingIndicator que adiciona um indicador de carregamento a um componente existente.


Vamos escrever uma assinatura de tipo para esta função. A função aceita uma entrada de componente e retorna um componente também. Para representar um componente, podemos usar o tipo React ComponentType .


ComponentType é um tipo genérico que é parametrizado pelo tipo de propriedades do componente. withLoadingIndicator aceita um componente e retorna um novo componente que exibe o componente original ou o indicador de carregamento. A decisão sobre o que exibir é feita com base no valor da nova propriedade lógica - isLoading . Portanto, o componente retornado precisa das mesmas propriedades que o original, apenas a nova propriedade isLoading é adicionada.


Vamos finalizar o tipo. withLoadingIndicator aceita um componente do tipo ComponentType<P> , em que P indica o tipo de propriedade. withLoadingIndicator retorna um componente com propriedades avançadas do tipo P & { isLoading: boolean } .


 const withLoadingIndicator = <P>(Component: ComponentType<P>) : ComponentType<P & { isLoading: boolean }> => ({ isLoading, ...props }) => { /* ... */ } 

Ao lidar com os tipos de funções, fomos forçados a pensar sobre o que estará em sua entrada e o que estará em sua saída. Em outras palavras, tivemos que projetar uma função . Escrever sua implementação agora é fácil.


Comece com tipos. Deixe os tipos forçarem você a projetar primeiro e só então escrever a implementação.


Tome rigor


Os três primeiros mandamentos exigem que você preste atenção especial aos tipos. Felizmente, ao resolver esse problema, você não precisa fazer tudo sozinho. Geralmente, o compilador TypeScript permite que você saiba quando seus tipos estão mentindo ou quando não são precisos o suficiente.


Você pode ajudar o compilador a fazer isso ainda melhor, incluindo o sinalizador --strict . Este é um meta flag que --noImplicitAny todas as opções estritas de verificação de tipo: --noImplicitAny , --noImplicitThis , --alwaysStrict , --strictBindCallApply , --strictNullChecks , --strictFunctionTypes e --strictPropertyInitialization .


O que as bandeiras fazem? De um modo geral, sua inclusão leva a um aumento no número de erros de compilação TypeScript. E isso é bom! Mais erros de compilação - mais ajuda do compilador.


Vamos ver como ativar o sinalizador --strictNullChecks ajuda a detectar um falso no código.


 function getUser(id: number): User { if (id >= 0) { return { name: 'John', age: 12 }; } else { return undefined; } } 

O tipo getUser garante que a função sempre retorne um objeto do tipo User . No entanto, observe a implementação: uma função também pode retornar undefined !


Felizmente, ativar o sinalizador --strictNullChecks resulta em um erro de compilação:


 Type 'undefined' is not assignable to type 'User'. 

O compilador TypeScript detecta falsidades. Para se livrar desse erro, diga honestamente toda a verdade:


 function getUser(id: number): User | undefined { /* ... */ } 

Aceite o rigor da verificação de tipo. Deixe o compilador protegê-lo contra erros.


Mantenha-se atualizado


O TypeScript está se desenvolvendo em um ritmo muito rápido. Um novo lançamento é lançado a cada dois meses. Cada versão traz melhorias significativas no idioma e novos recursos.


Geralmente, os novos recursos do idioma permitem definir tipos com mais precisão e verificá-los com mais rigor.


Por exemplo, na versão 2.0, tipos de união discriminados foram introduzidos (eu os mencionei no mandamento Seja preciso ).


A versão 3.2 introduziu o sinalizador do compilador --strictBindCallApply , que inclui a digitação correta para as funções de bind , call e apply .


A versão 3.4 melhorou a inferência de tipo em funções de ordem superior , o que facilitou o uso de tipos exatos ao escrever código em um estilo funcional.


Minha posição é que conhecer os recursos de linguagem introduzidos nas versões recentes do TypeScript realmente vale a pena. Muitas vezes, isso pode ajudá-lo a seguir os outros quatro mandamentos da lista.


Um bom ponto de partida é o roteiro oficial do TypeScript . Também será bom verificar regularmente a seção TypeScript no Microsoft Devblog , pois todos os anúncios de lançamento estão lá.


Mantenha-se atualizado com os novos recursos do idioma e deixe esse conhecimento funcionar para você.


Sumário


Espero que você ache a lista útil. Como sempre e em tudo, não se deve seguir cegamente esses mandamentos. Mas acredito firmemente que essas regras o tornarão um desenvolvedor TypeScript melhor.


Ficarei feliz em ver seus pensamentos sobre esse assunto nos comentários.


Bônus


Você gostou deste artigo sobre o TypeScript? Tenho certeza de que você também gostará deste PDF gratuito: 10 erros de desenvolvimento TypeScript que tornam seu código inseguro.

Source: https://habr.com/ru/post/pt461565/


All Articles