
JavaScript é um dos idiomas com digitação dinâmica. Essas linguagens são convenientes para o desenvolvimento rápido de aplicativos, mas quando várias equipes assumem o desenvolvimento de um grande projeto, é melhor escolher uma das ferramentas para verificação de tipo desde o início.
Você pode começar a desenvolver o código TypeScript ou incluí-lo em um projeto do Flow. TypeScript é uma versão compilada do JavaScript desenvolvida pela Microsoft. O Flow, diferentemente do TypeScript, não é uma linguagem, mas uma ferramenta que permite analisar o código e verificar os tipos. Você pode encontrar muitos artigos e vídeos na rede sobre essas abordagens, bem como um guia sobre como começar a digitar. Neste artigo, gostaríamos de dizer por que o Flow não nos convinha e como começamos a mudar para o Typecript.
Um pouco de história
Em 2016, começamos a desenvolver um cliente Web baseado em React / Redux para o nosso sistema ECM. O fluxo foi selecionado para testar a digitação pelos seguintes motivos:
- React e Flow são produtos da mesma empresa Facebook.
- O fluxo se desenvolveu mais ativamente.
- O fluxo é facilmente integrado ao projeto.
Mas o projeto cresceu, o número de equipes de desenvolvimento aumentou e surgiram vários problemas ao usar o Flow:
- Verificação do tipo de plano de fundo O Flow usou muitos recursos do PC. Como resultado, alguns desenvolvedores o desativaram e começaram a verificar conforme necessário.
- Houve situações em que demorou tanto tempo para alinhar o código com o Flow quanto escrever o próprio código.
- O código começou a aparecer no projeto, necessário apenas para passar no teste de fluxo. Por exemplo, verifique novamente se há nulo:
foo() { if (this.activeFormContainer == null) { return; }
- A maioria dos desenvolvedores usou o editor de código do Visual Studio Code, no qual o Flow não tem um suporte tão bom quanto o TypeScript. A conclusão automática (IntelliSense) nem sempre funcionava durante o desenvolvimento e a navegação de código era instável. Gostaria de ter a mesma conveniência de desenvolvimento que ao escrever em C # no Visual Studio.
Alguns desenvolvedores tiveram a ideia de tentar mudar para o TypeScript. Para testar a ideia da transição e convencer a gerência, decidimos experimentar o protótipo.
Protótipo
Queríamos testar duas idéias sobre protótipos:
- Tente traduzir o projeto inteiro.
- Configure o projeto para poder usar o Flow e o Typcript em paralelo.
Para a primeira idéia, era necessário um utilitário que convertesse todos os arquivos do projeto. Na rede encontrou um deles. A julgar pela descrição, ela poderia traduzir a maioria, mas algumas das mudanças teriam que ser editadas por nós mesmos ou o próprio utilitário deveria ser adicionado. Conseguimos converter um projeto de teste com um pequeno número de arquivos. Mas o projeto real não pôde ser compilado, seria necessário editar muitos arquivos. Decidimos não continuar nessa direção, pois:
- Ainda havia muito a ser feito! E enquanto finalizaremos o projeto, as equipes restantes continuarão a desenvolver novas funcionalidades, corrigir bugs, escrever testes. Além disso, levaria muito tempo para mesclar arquivos.
- Mesmo se traduzíssemos o projeto dessa maneira, quanto trabalho nossos testadores teriam que fazer!
Embora tenhamos abandonado essa opção, adquirimos experiência útil nela. Tornou-se clara a quantidade aproximada de trabalho que precisa ser feita para traduzir cada arquivo. Aqui está a aparência da tradução de um componente React simples.

Como você pode ver, não há muitas mudanças. Basicamente, eles são os seguintes:
- remove // @ flow;
- substitua o tipo por uma interface mais familiar;
- adicionar modificadores de acesso;
- substitua tipos por tipos de bibliotecas ts (do exemplo na figura: manipuladores de eventos e os próprios eventos).
A implementação da segunda ideia permitiria maior desenvolvimento, mas já no TypeScript, e em segundo plano, para traduzir lentamente a base de código existente. Isso proporcionou várias vantagens:
- Fácil de traduzir, sem medo de perder alguma coisa.
- Fácil de testar.
- Fácil de mesclar alterações.
Mas não ficou completamente claro se o projeto poderia ser configurado para funcionar com dois tipos de digitação em paralelo. Uma pesquisa na Internet não levou a nada concreto, então eles começaram a resolver por conta própria. Em teoria, o analisador de fluxo verifica apenas arquivos com a extensão js / jsx e contém um comentário:
Para o compilador TypeScript, os arquivos devem ter a extensão ts / tsx. Daí resulta que ambas as abordagens para digitar devem funcionar simultaneamente e não interferir entre si. Com base nisso, configuramos o ambiente do projeto. Usando a experiência do primeiro protótipo, traduzimos alguns arquivos. Compilou o projeto, lançou o cliente - tudo funcionou como antes!
Luz verde
E um belo dia - o dia do planejamento do sprint, nossa equipe possui uma história do usuário "Iniciar a mudança para TypeScript" no backlog, com a seguinte lista de trabalhos:
- Configure o webpack.
- Configure tslint.
- Configure um ambiente de teste.
- Traduza arquivos para TypeScript.
Configuração do Webpack
O primeiro passo é ensinar ao webpack como lidar com arquivos com a extensão ts / tsx. Para isso, adicionamos uma regra à seção de regras do arquivo de configuração. Ts-loader usado originalmente:
Para acelerar a montagem, a verificação de tipo foi desativada:
transpileOnly: true
, porque O IDE já indica erros ao escrever o código.
Mas quando começamos a traduzir nossas ações Redux, ficou claro que elas precisavam do
plugin babel-plugin-transform-class-display-name . Este plugin adiciona uma propriedade displayName estática a todas as classes. Após a conversão, apenas as ações do ts-loader foram processadas e isso não permitiu que plugins babel fossem aplicados a eles. Como resultado, abandonamos o ts-loader e estendemos a regra existente para js / jsx adicionando
babel / preset-typescript:
Para que o compilador TypeScript funcione corretamente, você precisa adicionar o arquivo de configuração tsconfig.json, que foi retirado da documentação.
Configurar Tslint
O código escrito usando o Flow foi verificado adicionalmente usando o eslint. O TypeScript tem sua contrapartida, tslint. Inicialmente, eu queria transferir todas as regras de eslint para tslint. Houve uma tentativa de sincronizar regras através do plug-in tslint-eslint-rules, mas a maioria das regras não é suportada. Também é possível usar o eslint para verificar arquivos ts usando o typescript-eslint-parser. Mas, infelizmente, apenas um analisador pode ser conectado ao eslint. Se você usar apenas ts-parser para todos os tipos de arquivos, muitos erros estranhos aparecerão nos arquivos js e ts. Como resultado, usamos o conjunto de regras recomendado, expandido para nossos requisitos:
// tslint.json "extends": ["tslint:recommended", "tslint-react"]
Traduzir um arquivo para TypeScript
Agora está tudo pronto e você pode começar a traduzir arquivos. Para começar, decidimos transferir um pequeno componente React, que é usado durante todo o projeto. A escolha recaiu sobre o componente "Button".

Encontramos um problema durante o processo de tradução: nem todas as bibliotecas de terceiros têm digitação TypeScript, por exemplo, bem-cn-lite. No recurso
TypeSearch da Microsoft, não foi possível encontrar a biblioteca de tipos. para quase todas as bibliotecas necessárias, encontramos e conectamos bibliotecas do tipo ts. Uma solução era conectar via exigir:
const b = require('bem-cn-lite');
Mas, ao mesmo tempo, o problema com a falta de tipos não foi resolvido. Portanto, geramos um "stub" para os tipos, usando o utilitário
dts-gen :
dts-gen -m bem-cn-lite
O utilitário gerou um arquivo com a extensão * .d.ts. O arquivo foi colocado na pasta @types e configurado tsconfig.json:
// tsconfig.json "typeRoots": [ "./@types", "./node_modules/@types" ]
Em seguida, por analogia com o protótipo, traduzimos o componente. Compilou o projeto, lançou o cliente - tudo funcionou! Mas os testes foram interrompidos.
Configuração do ambiente de teste
Para testar o aplicativo, usamos o Storybook e o Mocha.
O livro de histórias é usado para testes de regressão visual (
artigo ). Como o próprio projeto, ele é construído usando o webpack e possui seu próprio arquivo de configuração. Portanto, para trabalhar com arquivos ts / tsx, ele precisava ser configurado por analogia com a configuração do próprio projeto.
Enquanto usamos o ts-loader para construir o projeto, paramos de executar os testes do Mocha. Para resolver esse problema, adicione ts-node ao ambiente de teste:
// mocha.opts --require @babel/polyfill --require @babel/register --require test/index.js --require tsconfig-paths/register --require ts-node/register/transpile-only --recursive --reporter mochawesome --reporter-options reportDir=../../bin/TestResults,reportName=js-test-results,inlineAssets=true --exit
Mas depois de mudar para Babel, você pode se livrar dele.
Os problemas
No processo de tradução, encontramos um grande número de problemas com graus variados de complexidade. Eles estavam relacionados principalmente à nossa falta de experiência com o TypeScript. Aqui estão alguns deles:
- Importar componentes / funções de diferentes tipos de arquivos.
- Tradução de componentes de ordem superior.
- Perda do histórico de alterações.
Importar componentes / funções de diferentes tipos de arquivo
Ao usar componentes / funções de diferentes tipos de arquivo, tornou-se necessário especificar a extensão do arquivo:
import { foo } from './utils.ts'
Isso permite adicionar extensões válidas aos arquivos de configuração webpack e eslint:
Tradução de componentes de ordem superior
De todos os tipos de arquivo, a tradução do HOC (Componente de Ordem Superior) causou mais problemas. Esta é uma função que pega um componente na entrada e retorna um novo componente. É usado principalmente para reutilizar a lógica, por exemplo, pode ser uma função que adiciona a capacidade de selecionar elementos:
const MyComponentWithSeletedItem = withSelectedItem(MyComponent);
Ou a conexão mais famosa, da biblioteca Redux. A digitação de tais funções não é trivial e requer a conexão de uma biblioteca adicional para trabalhar com tipos. Não descreverei o processo de tradução em detalhes, pois você pode encontrar muitos manuais sobre o assunto na rede. Em resumo, o problema é que essa função é abstrata: qualquer componente com qualquer conjunto de propriedades pode aceitar entrada. Pode ser um componente Button com propriedades title e onClick ou um componente Picture com propriedades alt e imgUrl. O conjunto dessas propriedades não é conhecido antecipadamente; somente as propriedades adicionadas pela própria função são conhecidas. Para que o compilador TypeScript não jure ao usar componentes obtidos com a ajuda de tais funções, é necessário "cortar" as propriedades que a função adiciona ao tipo de retorno.
Para fazer isso, você precisa:
- Puxe estas propriedades para a interface:
interface IWithSelectItem { selectedItem: number; handleSelectedItemChange: (id: number) => void; }
- Remova todas as propriedades que entram na interface IWithSelectItem da interface do componente. Para fazer isso, você pode usar a operação Diff <T, U> da biblioteca de tipos de utilitário .
React.ComponentType<Diff<TPropsComponent, IWithSelectItem>>
Perda do histórico de alterações
Para trabalhar com fontes, por exemplo, revisão de código, usamos o Team Foundation Server. Ao traduzir arquivos, encontramos um recurso desagradável. Em vez de um arquivo alterado, dois aparecem no pool de solicitações:
- remote - a versão antiga do arquivo;
- criado - nova versão.

Esse comportamento é observado se houver muitas alterações no arquivo (similaridade <50%), por exemplo, para arquivos pequenos. Para resolver esse problema, tentamos usar:
- comando git mv
- execute duas confirmações: a primeira está alterando a extensão do arquivo, a segunda com correções imediatas.
Infelizmente, ambas as abordagens não nos ajudaram.
Sumário
Use Flow ou TypeScript - todo mundo decide por si mesmo, ambas as abordagens têm seus prós e contras. Escolhemos o TypeScript para nós mesmos. E você ficou convencido por sua própria experiência: se você escolheu uma das abordagens e de repente percebeu, mesmo depois de três anos que ela não combina com você, você sempre pode mudar. E para uma transição mais suave, você pode configurar o projeto, como nós, para trabalhar em paralelo.
No momento da redação, ainda não havia mudado completamente para o TypeScript, mas já reescrevemos a parte principal - o "núcleo" do projeto. Na base de código, você pode encontrar exemplos da tradução de todos os tipos de arquivos, de um simples componente de reação a componentes de ordem superior. Além disso, foi realizado treinamento entre todas as equipes de desenvolvimento, e agora cada equipe, como parte de sua tarefa, transfere parte do projeto para essas tarefas.
Planejamos concluir a transição antes do final do ano, traduzir testes e um livro de histórias e talvez até escrever algumas de nossas regras tslint.
De acordo com meus sentimentos pessoais, posso dizer que o desenvolvimento começou a demorar menos, a verificação do tipo é feita em tempo real, sem carregar o sistema, e as mensagens de erro para mim pessoalmente se tornaram mais compreensíveis.