Infelizmente, devemos admitir que em 2021 o flow já é significativamente inferior ao TypeScript, tanto em popularidade quanto no suporte de uma variedade de utilitários (e bibliotecas), e é hora de
Por que você precisa de segurança de tipo em JavaScript?
JavaScript é uma linguagem maravilhosa. Não, não gosto disso. O ecossistema construído em torno do JavaScript é ótimo. Para 2021, ela realmente admira o fato de que você pode usar os recursos mais modernos da linguagem e, em seguida, alterando uma configuração do sistema de compilação, transpilar o arquivo executável para suportar sua execução em versões anteriores de navegadores, incluindo o IE8 , não será de noite, lembre-se. Você pode "escrever em HTML" (ou seja, JSX) e, em seguida, usar o utilitário
babel
(ou
tsc
) substituir todas as tags por construções JavaScript corretas, como chamar a biblioteca React (ou qualquer outra, mas mais sobre isso em outro post).
Por que o JavaScript é uma boa linguagem de script que roda em seu navegador?
- JavaScript não precisa ser "compilado". Você apenas adiciona construções JavaScript e o navegador deve entendê-las. Isso imediatamente dá um monte de coisas convenientes e quase gratuitas. Por exemplo, a depuração diretamente no navegador, que não é responsabilidade do programador (que não deve esquecer, por exemplo, de incluir um monte de opções de depuração do compilador e bibliotecas correspondentes), mas do desenvolvedor do navegador. Você não precisa esperar 10-30 minutos (tempo real para C / C ++) enquanto seu projeto de linha de 10k é compilado para tentar escrever algo diferente. Basta alterar a linha, recarregar a página do navegador e observar o novo comportamento do código. E no caso de usar, por exemplo, webpack, a página também será recarregada para você. Muitos navegadores permitem que você altere o código dentro da página usando seus devtools.
- - . 2021 . Chrome/Firefox, , , 5% (enterprise-) 30% (UI/) , .
- JavaScript , . — ( worker'). , 100% CPU ( UI ), , , Promise/async/await/etc.
- Ao mesmo tempo, nem mesmo considero a questão de por que o JavaScript é importante. Afinal, com a ajuda do JS, você pode: validar formulários, atualizar o conteúdo da página sem recarregá-lo totalmente, adicionar efeitos de comportamento não padrão, trabalhar com áudio e vídeo, e você pode até mesmo escrever todo o cliente de seu aplicativo corporativo em JavaScript.
Como acontece com quase qualquer linguagem de script (interpretada), em JavaScript você pode ... escrever código quebrado. Se o navegador não alcançar esse código, não haverá mensagem de erro, nenhum aviso, nada. Por um lado, isso é bom. Se você tiver um site muito grande, mesmo um erro de sintaxe no código do manipulador de clique de botão não deve fazer com que o site não seja totalmente carregado pelo usuário.
Mas, claro, isso é ruim. Porque o próprio fato de ter algo não funcionando em algum lugar do site é ruim. E seria ótimo, antes que o código chegue a um site de trabalho, verificar todos os scripts no site e certificar-se de que pelo menos compilem. E idealmente - e trabalhar. Para isso, uma variedade de conjuntos de utilitários são usados (meu conjunto favorito é npm + webpack + babel / tsc + karma + jsdom + mocha + chai).
Se vivemos em um mundo ideal, então todos os scripts em seu site, mesmo os de uma linha, são cobertos com testes. Mas, infelizmente, o mundo não é ideal, e para toda aquela parte do código que não é coberta pelos testes, só podemos contar com algum tipo de ferramenta de verificação automatizada. Que pode verificar:
- JavaScript. , JavaScript, , , . /// .
- . , , . , :
var x = null; x.foo();
. — null .
Além dos erros de semântica, pode haver erros ainda mais terríveis: erros lógicos. Quando o programa é executado sem erros, mas o resultado não é o esperado. Clássico com adição de strings e números:
console.log( input.value ) // 1
console.log( input.value + 1 ) // 11
As ferramentas de análise de código estático existentes (eslint, por exemplo) podem tentar rastrear um número significativo de erros potenciais que um programador comete em seu código. Por exemplo:
- Proibindo um loop for infinito com uma condição de término de loop incorreta
- Proibindo funções assíncronas como argumentos para um construtor de promessa
- Proibindo atribuições em condições
- e outros
Observe que todas essas regras são essencialmente restrições que o linter impõe ao programador. Ou seja, o linter realmente reduz os recursos da linguagem JavaScript para que o programador cometa menos erros potenciais. Se você habilitar todas as regras, será impossível fazer atribuições em condições (embora o JavaScript inicialmente permita isso), use chaves duplicadas em literais de objeto e nem mesmo pode ser chamado
console.log()
.
Adicionar tipos de variáveis e verificação de tipo de chamadas são limitações adicionais da linguagem JavaScript para reduzir possíveis erros.
Tentando multiplicar um número por uma string
Uma tentativa de acessar uma propriedade inexistente (não descrita no tipo) de um objeto.
Uma tentativa de chamar uma função com um tipo de argumento incompatível.
Se escrevermos este código sem um verificador de tipo, o código será transpilado com êxito. Nenhum meio de análise de código estático, se não usar (explícita ou implicitamente) informações sobre os tipos de objetos, não será capaz de encontrar esses erros.
Ou seja, adicionar digitação ao JavaScript adiciona restrições adicionais ao código que o programador grava, mas permite que você encontre erros que poderiam ocorrer durante a execução do script (ou seja, provavelmente no navegador do usuário).
Capacidades de digitação de JavaScript
| Fluxo | TypeScript | |
|---|---|---|
| Capacidade de definir o tipo de uma variável, argumento ou tipo de retorno de uma função | |
|
| Capacidade de descrever seu tipo de objeto (interface) | |
|
| Restringindo valores para um tipo | |
|
| Extensão de nível de tipo separada para enumerações |
|
|
| Tipos de "adição" |
|
|
| "Tipos" adicionais para casos complexos |
|
|
Ambos os mecanismos para suporte ao tipo JavaScript têm aproximadamente os mesmos recursos. No entanto, se você vem de linguagens fortemente tipadas, mesmo o JavaScript tipado tem uma diferença muito importante do Java: todos os tipos descrevem essencialmente interfaces, ou seja, uma lista de propriedades (e seus tipos e / ou argumentos). E se duas interfaces descrevem as mesmas propriedades (ou compatíveis), elas podem ser usadas no lugar uma da outra. Ou seja, o código a seguir está correto em JavaScript digitado, mas claramente incorreto em Java ou, digamos, C ++:
type MyTypeA = { foo: string; bar: number; } type MyTypeB = { foo: string; } function myFunction( arg : MyTypeB ) : string { return `Hello, ${arg.foo}!`; } const myVar : MyTypeA = { foo: "World", bar: 42 } as MyTypeA; console.log( myFunction( myVar ) ); // "Hello, World!"
Este código está correto do ponto de vista do JavaScript digitado, uma vez que a interface MyTypeB requer uma propriedade
foo
com um tipo
string
, enquanto uma variável com a interface MyTypeA o faz.
Este código pode ser reescrito um pouco mais curto, usando uma interface literal para uma variável
myVar
.
type MyTypeB = { foo: string; } function myFunction( arg : MyTypeB ) : string { return `Hello, ${arg.foo}!`; } const myVar = { foo: "World", bar: 42 }; console.log( myFunction( myVar ) ); // "Hello, World!"
O tipo de variável
myVar
neste exemplo é uma interface literal
{ foo: string, bar: number }
. Ele ainda é compatível com a interface esperada de um argumento de
arg
função
myFunction
, portanto, esse código está livre de erros do ponto de vista de, por exemplo, TypeScript.
Esse comportamento reduz significativamente o número de problemas ao trabalhar com diferentes bibliotecas, código personalizado e até mesmo apenas chamar funções. Um exemplo típico é quando alguma biblioteca define opções válidas e as passamos como um objeto de opções:
// - interface OptionsType { optionA?: string; optionB?: number; } export function libFunction( arg: number, options = {} as OptionsType) { /*...*/ }
// import {libFunction} from "lib"; libFunction( 42, { optionA: "someValue" } );
Observe que o tipo
OptionsType
não é exportado da biblioteca (nem é importado para o código personalizado). Mas isso não impede que você chame a função usando a interface literal para o segundo argumento da
options
função e para o sistema de digitação - para verificar a compatibilidade de tipo deste argumento. Tentar fazer algo assim em Java causará uma confusão clara entre o compilador.
Como funciona do ponto de vista do navegador?
Nem o TypeScript da Microsoft nem o fluxo do Facebook são compatíveis com os navegadores. Bem como as extensões de linguagem JavaScript mais recentes ainda não encontraram suporte em alguns navegadores. Então, em primeiro lugar, como esse código é verificado quanto à exatidão e, em segundo lugar, como ele é executado pelo navegador?
A resposta é trapacear. Todo código JavaScript "não padrão" passa por um conjunto de utilitários que transformam o código "não padrão" (desconhecido para os navegadores) em um conjunto de instruções que os navegadores entendem. E para a digitação, toda a "transformação" consiste no fato de que todos os refinamentos de tipo, todas as descrições de interface, todas as restrições do código são simplesmente removidas. Por exemplo, o código do exemplo acima se transforma em ...
/* : type MyTypeA = { foo: string; bar: number; } */ /* : type MyTypeB = { foo: string; } */ function myFunction( arg /* : : MyTypeB */ ) /* : : string */ { return `Hello, ${arg.foo}!`; } const myVar /* : : MyTypeA */ = { foo: "World", bar: 42 } /* : as MyTypeA */; console.log( myFunction( myVar ) ); // "Hello, World!"
Essa.
function myFunction( arg ) { return `Hello, ${arg.foo}!`; } const myVar = { foo: "World", bar: 42 }; console.log( myFunction( myVar ) ); // "Hello, World!"
Essa conversão geralmente é feita de uma das seguintes maneiras.
- Para remover informações de tipo do fluxo, o plugin babel é usado: @ babel / plugin-transform-flow-strip-types
- Você pode usar uma das duas soluções para trabalhar com TypeScript. Primeiramente pode-se usar o babel e o plugin @ babel / plugin-transform-typescript
- Em segundo lugar, em vez de babel, você pode usar o próprio transpiler da Microsoft chamado tsc . Este utilitário é integrado ao processo de construção do aplicativo em vez do babel.
Exemplos de configurações de projeto para fluxo e TypeScript (usando tsc).
| Fluxo | TypeScript |
|---|---|
| webpack.config.js | |
|
|
| Configurações do transpiler | |
| babel.config.js | tsconfig.json |
|
|
| .flowconfig | |
|
|
A diferença entre as abordagens babel + strip e tsc é pequena em termos de montagem. No primeiro caso, usa-se babel, no segundo, será tsc.
Mas há uma diferença se um utilitário como o eslint for usado. O TypeScript para linting com eslint tem seu próprio conjunto de plug-ins que permitem encontrar ainda mais bugs. Mas exigem que na hora da análise pelo linter ele tenha informações sobre os tipos de variáveis. Para fazer isso, apenas tsc deve ser usado como analisador de código, não babel. Mas se o tsc for usado para o linter, será errado usar o babel para a construção (o zoológico de utilitários usados deve ser mínimo!).
| Fluxo | TypeScript |
|---|---|
| .eslint.js | |
|
|
Tipos para bibliotecas
Quando uma biblioteca é publicada no repositório npm, é a versão JavaScript que é publicada. Presume-se que o código publicado não precisa ser modificado para ser usado em um projeto. Ou seja, o código já passou pela traspilation necessária via babel ou tsc. Mas então as informações sobre os tipos no código já foram perdidas. O que fazer?
No fluxo, presume-se que, além da versão "pura" do JavaScript, a biblioteca conterá arquivos com a extensão
.js.flow
contendo o código de fluxo de origem com todas as definições de tipo. Então, ao analisar o fluxo, poderá conectar esses arquivos para verificação de tipo, e na construção do projeto e sua execução, eles serão ignorados - serão utilizados arquivos JS comuns. Você pode adicionar arquivos .flow à biblioteca por meio de uma cópia simples. No entanto, isso aumentará significativamente o tamanho da biblioteca em npm.
No TypeScript, não é sugerido manter os arquivos de origem lado a lado, mas apenas uma lista de definições. Se houver um arquivo
myModule.js
, ao analisar o projeto, o TypeScript procurará um arquivo próximo
myModule.js.d.ts
, no qual espera ver as definições (mas não o código!) De todos os tipos, funções e outras coisas que são necessárias para analisar os tipos. O transpiler tsc é capaz de criar tais arquivos a partir do TypeScript de origem por conta própria (veja a opção
declaration
na documentação).
Tipos para bibliotecas legadas
Para fluxo e TypeScript, há uma maneira de adicionar declarações de tipo para as bibliotecas que não contêm inicialmente essas descrições. Mas isso é feito de maneiras diferentes.
Para o fluxo, não existe um método “nativo” suportado pelo próprio Facebook. Mas existe um projeto com tipo de fluxo que coleta essas definições em seu repositório. Na verdade, uma forma paralela para o npm criar versões de tais definições, e também uma forma "centralizada" de atualização não muito conveniente.
No TypeScript, a maneira padrão de escrever tais definições é publicá-las em pacotes npm especiais com o prefixo "@types"... Para adicionar uma descrição dos tipos de uma biblioteca ao seu projeto, basta conectar a @tipos-library correspondente, por exemplo,
@types/react
para React ou
@types/chai
para chai.
Comparação de fluxo e TypeScript
Uma tentativa de comparar o fluxo e o TypeScript. Os fatos selecionados são coletados do artigo "TypeScript VS Flow" de Nathan Sebhastian, alguns são coletados independentemente.
Suporte nativo em várias estruturas. Native - nenhuma abordagem adicional com um ferro de soldar e bibliotecas e plug-ins de terceiros.
Vários governantes
| Fluxo | TypeScript | |
|---|---|---|
| Contribuidor principal | Microsoft | |
| Local na rede Internet | flow.org | www.typescriptlang.org |
| Github | github.com/facebook/flow | github.com/microsoft/TypeScript |
| GitHub é iniciado | 21,3k | 70,1k |
| GitHub Forks | 1.8k | 9,2 k |
| Problemas do GitHub: aberto / fechado | 2,4k / 4,1k | 4,9k / 25,0k |
| StackOverflow ativo | 2289 | 146.221 |
| StackOverflow Frequent | 123 | 11451 |
Olhando para esses números, simplesmente não tenho o direito moral de recomendar o uso do fluxo. Mas por que eu mesmo usei? Porque costumava haver algo como tempo de execução de fluxo.
flow-runtime
flow-runtime é um conjunto de plug-ins para babel que permite embutir tipos de fluxo em tempo de execução, usá-los para definir tipos de variáveis em tempo de execução e, o mais importante para mim, permite que você verifique os tipos de variáveis em tempo de execução. Isso permitido em tempo de execução durante, por exemplo, autotestes ou testes manuais, para detectar bugs adicionais no aplicativo.
Ou seja, bem no tempo de execução (no assembly de depuração, é claro), o aplicativo verifica explicitamente todos os tipos de variáveis, argumentos, resultados de chamadas para funções de terceiros e tudo, tudo, tudo, para conformidade com esses tipos.
Infelizmente, para o novo ano de 2021, o autor do repositório adicionou informaçõesque ele não está mais envolvido no desenvolvimento deste projeto e, em geral, muda para o TypeScript. Na verdade, o último motivo para continuar no fluxo tornou-se obsoleto para mim. Bem, bem-vindo ao TypeScript.