- Olá a todos, meu nome é Lesha, sou desenvolvedor frontend. Vamos começar. Vou contar um pouco sobre mim e o projeto em que trabalho. Flow está aprendendo inglês com Yandex.Practicum. O lançamento ocorreu em abril deste ano. A frente foi escrita diretamente em TypeScript, antes disso não havia código.
Um pouco sobre minha experiência. Em algum ano distante, comecei a programar. Um ano em 2013, ele começou a trabalhar.
Quase imediatamente percebi que estava muito mais interessado na frente, mas tinha experiência com linguagens de digitação estática. Comecei a usar JavaScript e essa tipagem estática não estava lá. Pareceu-me conveniente, gostei.
Em uma mudança de projeto, comecei a trabalhar usando TypeScript. Vou falar sobre os prós que descobri ao mudar para o TypeScript. Mais fácil de entender o projeto. Temos uma descrição dos tipos de dados que são usados no projeto e as conversões entre eles.
É mais seguro fazer alterações no código: quando houver alterações no backend ou apenas em alguma parte do código, o TypeScript irá destacar os locais onde os erros apareceram.
Há menos preocupação com os tipos. Quando criamos uma nova funcionalidade, definimos imediatamente os tipos com os quais as funções funcionam, e podemos nos preocupar menos se receberemos dados diferentes.
Não há medo de que nulo ou indefinido venha, não precisamos ser paranóicos, inserir se desnecessário e construções semelhantes.
No início deste ano, mudei para Flow. TypeScript também é usado aqui, mas não o reconheci nem um pouco. Por quê? Ele foi muito gentil comigo, um quarto dos erros do cliente estavam relacionados a nulos e indefinidos. Comecei a descobrir qual era o problema e encontrei uma linha na configuração que mudou todo o comportamento do TypeScript.
Essa é a inclusão de strict. Não estava lá, mas precisava ser ativado para melhorar a verificação.
TypeScript: estrito
O que é estrito? Em que consiste?
Este é um conjunto de sinalizadores que podem ser ativados individualmente, mas na minha opinião são todos muito úteis. noImplicitAny - antes de habilitar este sinalizador, podemos declarar, por exemplo, funções cujos parâmetros estarão implícitos, como any. Se ativarmos esse sinalizador, devemos adicionar a digitação em locais onde o TypeScript não pode calcular o tipo a partir do contexto.
Ou seja, no segundo caso, devemos adicionar a tipagem, uma vez que não existe um contexto como tal. No terceiro caso, onde temos um mapa, não podemos adicionar a digitação para a, pois é claro a partir do contexto que haverá um tipo de número.
noImplicitThis. O TypeScript nos obriga a digitar isso quando não há contexto. Quando o contexto é, ou seja, é um objeto ou uma classe, não precisamos fazer isso.
alwaysStrict. Adiciona “use strict” a todos os arquivos. Mas isso afeta como o JavaScript executa nosso código. (...)
strictBindCallApply. Por algum motivo, antes de ativar esta opção, o TypeScript não verifica vincular, aplicar e chamar tipos. Depois de ligá-lo, ele os verifica e não permite que façamos essas coisas desagradáveis.
strictNullChecks é, em minha opinião, a verificação mais necessária. Obriga-nos a indicar na digitação os locais onde podem vir nulos ou indefinidos. Antes da inclusão, podemos passar nulo ou indefinido onde não for especificado explicitamente e, portanto, obter um erro. Depois disso, o controle será muito melhor.
Em seguida, strictFunctionTypes. A situação aqui é um pouco mais complicada. Vamos imaginar que temos três funções. Um trabalha com animais, outro com cães e outro com gatos. Um cachorro e um gato são animais. Ou seja, será um erro trabalhar com um cão da mesma forma que com um gato, porque são diferentes. Funcionará corretamente com um cão, assim como com um animal.
A terceira opção é quando tentamos trabalhar com qualquer animal como um cachorro. Por algum motivo, é permitido inicialmente no TypeScript, mas se você ativar esta opção, será inválido e algumas verificações serão realizadas.
Em seguida, strictPropertyInitialization. Isso é para aulas. Ele nos obriga a definir valores iniciais, seja ao declarar uma propriedade ou em um construtor. Às vezes, há momentos em que você precisa contornar essa regra. Você pode usar um ponto de exclamação, mas, novamente, isso nos obriga a ser um pouco mais cuidadosos.
Então, descobri que precisamos habilitar o strict. Tento ligá-lo e muitos erros aparecem. Portanto, decidiu-se usar uma configuração transicional para estrita. Definimos rigoroso em três etapas.
Primeiro estágio: adicionamos “strict”: true ao tsconfig e, consequentemente, nosso ambiente de desenvolvimento nos avisa sobre os locais com um erro, que é causado precisamente pela ativação de strict.
Mas para o webpack, criamos um tsconfig especial, que estrito será falso, e o usamos ao construir. Ou seja, durante a montagem, nada quebra para nós, mas em nosso editor vemos esses erros. E podemos consertá-los imediatamente. Então, de vez em quando, passamos para o segundo estágio, isso é uma solução. Construímos nosso projeto com o tsconfig usual. Corrigimos alguns dos erros cometidos e repetimos tudo isso em nosso tempo livre.
Com essas ações, reduzimos até agora o número de nossos erros de 400 para 200. Estamos ansiosos para passar para o terceiro estágio - removendo webpackTsConfig e usando tsconfig ao compilar, mas com estrito habilitado.
TypeScript: pequenas sutilezas
Você pode falar um pouco sobre as pequenas sutilezas do TypeScript que não são cobertas pelo estrito, mas são difíceis de formalizar corretamente.
Vamos começar com o operador de ponto de exclamação. O que isso permite que você faça? Nesse caso, refere-se a um campo que pode ser indefinido, como se não pudesse ser indefinido. Faz sentido no modo estrito, quando tentamos acessar um campo, dizendo explicitamente: Tenho certeza que definitivamente não é nulo ou indefinido. Mas isso é ruim, porque se de repente for nulo ou indefinido, naturalmente obteremos um erro de tempo de execução.
ESLint nos ajudará a evitar tais coisas, simplesmente nos proibirá. Conseguimos. Como faço para corrigir o exemplo anterior agora?
Suponha que temos essa situação.
Existe um elemento, pode ser do tipo link ou extensão. Com nossa cabeça, entendemos que span é apenas texto e link é texto e link.
(figura)
Mas esquecemos de informar a linguagem TypeScript, então na função getItemHtml surge uma situação que no caso de link temos que dizer: href não é opcional, com certeza será. Este também é um local potencial para erros. Como corrigi-lo?
A primeira opção é corrigir a digitação, ou seja, indicar explicitamente ao TypeScript que um href é necessário para um link e um opcional para span.
E o ponto de exclamação não será necessário aqui.
Segunda opção de correção. Suponha que o tipo de item não seja descrito por nós e não possamos simplesmente pegá-lo e restringi-lo. Então, podemos reescrevê-lo de maneira semelhante.
Atenção: o cheque acabou de aparecer. Em seguida, vem o log que o programador não esperava com esse valor ao escrever este código, portanto, no futuro, veremos esse erro e tomaremos as medidas adequadas.
Em seguida, estamos tentando renderizar nosso item de alguma forma. Aqui, você pode simplesmente dar ao usuário um erro. Mas se esses dados forem insignificantes, você pode fazer um esboço como aqui.
Como
Mais distante. Também existe um operador como. O que isso permite que você faça?
Permite-te dizer - sei bem, existe tal e tal tipo - e também te levar a um erro.
Arrays
Os métodos de luta são os mesmos. Você precisa ter um pouco mais de cuidado com os arrays. O TypeScript não é uma panacéia, ele não verificará alguns pontos. Por exemplo, podemos nos referir a um elemento de matriz inexistente. Nesse caso, pegaremos o primeiro elemento da matriz e obteremos um erro neste código. Como podemos consertar isso?
Novamente, existem duas maneiras. A primeira forma é digitando. Dizemos que temos o primeiro elemento e, sem medo, nos referimos a esse elemento. Ou verificaremos, registraremos, se algo de repente estiver errado, se esperamos explicitamente um array não vazio.
Objetos
É o mesmo com objetos. Podemos declarar um objeto, que pode ter qualquer número de propriedades, e também obter um erro indefinido.
Novamente, você pode fazer indicações explícitas de quais propriedades são necessárias ou apenas verificar.
qualquer
Agora, o óbvio é algum.
Ele permite que você se refira a qualquer propriedade de um objeto como se não houvesse nenhuma digitação. Nesse caso, podemos fazer o que quisermos com x. E novamente dê um tiro no próprio pé, cometa erros.
Novamente, é melhor proibir isso explicitamente com ESLint. Mas há situações em que ele aparece sozinho.
Por exemplo, neste caso JSON.parse produz apenas este tipo any. O que pode ser feito?
Você pode simplesmente dizer: eu não acredito em você, vamos dizer melhor que eu não sei o que é e vou viver com isso. Como conviver com isso? Aqui está um exemplo hipotético.
Existe um usuário, o usuário tem um nome obrigatório e um e-mail opcional.
Estamos escrevendo a função parseUser. Ele pega uma string JSON e retorna nosso objeto para nós. Agora começamos a verificar tudo isso. Primeiro, vemos a linha com parse e desconhecido, familiar para nós no slide anterior. Em seguida, começamos a verificar.
Se não for um objeto ou for nulo, emita um erro.
Além disso, se não houver propriedade de nome necessária ou se não for uma string, lançamos um erro. Aqui está a continuação do código.
Passamos a formar Usuário, uma vez que todos os campos obrigatórios já foram coletados.
A seguir, verificamos se existe um campo de email. Se for, verificamos seu tipo e, se o tipo não for compatível, geramos um erro. Se não houver e-mail, não enviamos nada e devolvemos o resultado. Tudo está bem. Mas você precisa escrever muito para o tipo mais simples.
E é preciso muitas verificações
Precisamos de muita validação porque uma solicitação JSON típica se parece com isso.
Sem mais delongas, isso é apenas fetch e json (). A conversão de any para SomeRequestResponse aparece em retorno. Isso também precisa ser combatido. Você pode fazer isso da maneira anterior ou um pouco diferente.
io-ts
É o mesmo por baixo do capô: usamos uma biblioteca especial para verificação de tipos. Nesse caso, é io-ts. Aqui está um exemplo simples de como trabalhar com isso.
Vamos pegar o tipo de usuário anterior e escrevê-lo na biblioteca que estamos usando. Sim, digitar é um pouco mais complicado aqui, mas duas condições devem ser atendidas simultaneamente. Deve ser um objeto com um campo de nome obrigatório e um objeto com um campo de email opcional. Como podemos verificar tudo isso?
Vamos escrever o mesmo parseUser. Neste caso, estamos usando o método User.decode. Passamos o objeto já emparelhado lá, ele retorna o resultado para nós. Talvez em um formato incomum. Um objeto do tipo Qualquer um, pode estar em dois estados. O primeiro está certo. Isso geralmente significa que tudo correu bem. à esquerda diz que não foi muito bem. Ambos os estados têm propriedades que nos permitem aprender mais. Se for bem sucedido, este é o resultado da execução, em caso de erro, um erro.
Verificamos se nossos resultados estão no estado esquerdo. Se estiverem, dizemos que ocorreu um erro. Além disso, se tudo estiver bem, simplesmente retornamos o resultado.
Exibindo erros
Sobre a exibição de erros. Você pode melhorar um pouco. Usaremos io-ts-repórteres para isso. É uma biblioteca escrita pelo mesmo autor que io-ts. Isso permite que o erro seja apresentado de forma bonita. O que ela faz? Mudamos o código aqui onde está o pato. Ele pega o resultado e retorna uma matriz de strings. Apenas juntamos em uma linha e exibimos. O que obtemos como resultado?
Suponha que estejamos passando null para uma string JSON.
Isso dará dois erros. Isso se deve à sutileza da implantação, pois fizemos intersecção. Os erros são bastante claros. Ambos dizem que esperávamos um objeto, mas obtivemos nulo. Só que para cada uma dessas condições ele dará um erro separadamente.
A seguir, vamos tentar passar um array vazio ali. Será igual.
Ele simplesmente nos dirá: Eu também esperava um objeto, mas recebi um array vazio.
Portanto, continuamos a ver o que acontecerá se começarmos a transmitir dados incorretos. Por exemplo, vamos passar um objeto vazio.
Agora haverá um erro sobre o fato de não termos o campo de nome obrigatório. Ele esperava que o campo de nome fosse do tipo string, mas termina com indefinido. Também é fácil entender a partir desse erro o que aconteceu.
A seguir, tentaremos passar um tipo incorreto aqui. Também obtemos um erro, quase o mesmo que no exemplo anterior.
Mas aqui ele claramente nos escreve o significado que transmitimos.
O que mais os io-ts podem fazer? Ele permite que você obtenha um tipo de TypeScript. Ou seja, adicionamos esta linha. Simplesmente adicionando typeof e também typeof, obtemos um tipo TypeScript que podemos usar posteriormente no aplicativo. Convenientemente.
O que mais essa biblioteca pode fazer? Converta tipos. Digamos que estejamos fazendo uma solicitação ao servidor. O servidor envia datas em formato de hora unix. E há uma biblioteca especial, novamente do criador da biblioteca io-ts: io-ts-types. Existem transformações que foram originalmente escritas e ferramentas para tornar essas transformações mais fáceis de escrever. Adicionamos um campo de data: vem do servidor como um número, e acabamos recebendo-o como um objeto Date.
Vamos descrever o tipo
Vamos ver o que há dentro desta biblioteca e tentar descrever o tipo mais simples.
Primeiro, vamos ver como geralmente é descrito. É descrito da mesma forma, um tanto complicado, visto que também é necessário para transformações. Além do servidor para o cliente, se considerarmos a interação com o servidor, e a transformação reversa, do cliente para o servidor.
Vamos simplificar um pouco nossa tarefa. Vamos apenas escrever o tipo que verifica. Nesse caso, vamos descobrir o que esses campos significam. nome - nome do tipo.
É necessário para exibir erros. Como vimos nos exemplos anteriores, os erros de alguma forma soletram o nome do tipo. Você pode especificá-lo aqui.
Em seguida, há a função de validação. Retira - digamos, do servidor - o valor desconhecido; usa um contexto para exibir corretamente o erro; e retorna um objeto Either em dois estados - um erro ou um valor validado.
Existem mais duas funções: is e encode. Eles são usados para transformá-los reversamente, mas não vamos tocá-los por enquanto.
Como o tipo de string mais simples pode ser representado? Definimos o nome como string e verificamos se é uma string. Com uma conversão direta, isso não será necessário, mas formalmente o escrevemos. E então apenas digitamos para verificar. Se for bem-sucedido, retornamos o resultado sucesso e, como resultado de um erro, falha. O contexto também é adicionado para que o erro seja exibido corretamente. E nós apenas retornamos a mesma coisa, porque não há transformação reversa.
Na prática
O que está em prática? Por que decidimos verificar os dados que vêm do servidor?
No mínimo, o banco de dados tem JSON. É claro que acreditamos que ele estará bem rodado e que será verificado em alguns pontos. Mas o formato pode mudar um pouco, não devemos quebrar o frontend ou descobrir os erros imediatamente para agirmos.
Temos Python no servidor sem digitação explícita. Com isso, também, às vezes pode haver pequenos problemas. E para não quebrar, podemos simplesmente verificar e proteger-nos adicionalmente, apenas no caso.
Não há documentação clara sobre as respostas do servidor. Provavelmente, o servidor está mais preocupado com o que virá com ele do que com o que ele dará. Sim, esse é mais o nosso problema - não quebrar.
O que encontramos? Já começamos a usar um pouco. Descobriu que o servidor nos dá um objeto vazio em vez de um array vazio. Acabei de olhar o código - escrito para retornar um objeto vazio.
Além disso - a ausência de alguns campos. Achamos que eram obrigatórios, mas acabaram sendo opcionais.
Um campo anulável estava simplesmente ausente em alguns casos. Ou seja, um campo opcional pode ser apresentado de duas maneiras: ou quando simplesmente não o passamos, ou quando passamos nulo. Também nem sempre nos ocorreu corretamente. Para não detectar erros no meio de nosso código, podemos detectar isso apenas nas solicitações.
O que nós temos agora? Já verificamos várias respostas do servidor e registramos se não gostamos de algo. Em seguida, analisamos isso e definimos tarefas: seja para alterar a digitação em nosso front-end, ou edições no back-end. Agora não alteramos os dados que vêm do servidor: se nulo vier em vez de uma string, não o alteramos, por exemplo, para uma string vazia.
Nossos planos são verificar e registrar, mas corrigir se houver um erro. Se recebermos dados incorretos, corrigiremos esse valor para que os usuários possam exibir pelo menos algo em vez de cair dentro do nosso código.
Resultados pequenos. Ativamos estrito para que o TypeScript nos ajude mais, exclua como, qualquer e o ponto de exclamação. Seremos mais cuidadosos com arrays e objetos no TypeScript e também verificaremos todos os dados externos. A propósito, não são apenas servidores. Você também pode verificar localStorage, mensagens que chegam em eventos. Por exemplo postMessage.
Obrigado pela atenção.