Análise estática com Flow — parte 3

Na segunda parte da série, vimos como integrar o Flow com o editor e um setup mínimo para manter a qualidade do código gerado através de indicadores de cobertura.
Caso seja seu primeiro contato com esta série, veja a primeira parte onde mostro como instalar e configurar o Flow.
Agora que já temos o workflow definido, vamos focar em algumas features que o Flow nos oferece a nível de sintaxe do código.
Como você já deve ter percebido nos exemplos anteriores, facilmente conseguimos deixar nosso código fortemente tipado e o Flow irá fazer a análise e nos mostrar os problemas em tempo real.
const foo:number = 'bar'
^ string. This type is incompatible with number
Porém, caso você não queira deixar todo seu código fortemente tipado por algum motivo, o Flow oferece uma análise de código através da inferência de tipos e consegue detectar possíveis erros que seriam gerados em tempo de execução, por exemplo:
const double = val => val * 2console.log(double(2)) // 4
console.log(double('foo'))
^ string. The operand of an arithmetic operation
must be a number.
Perceba que ele conseguiu analisar que a função double executa uma operação matemática e ao notar que seria executado uma chamada passando uma string como parâmetro ao invés de número, essa operação não seria possível e isso iria gerar um erro na aplicação durante sua execução. Toda essa análise estática foi feita mesmo sem o tipo estar fortemente definido no parâmetro da função double.
Tipos primitivos
Por dedução, os tipos primitivos ficam fáceis de serem declarados mesmo sem olhar a documentação, certo ?
const num:number = 1
const str:string = 'foo'
const bool:boolean = true
E null e undefined, como ficam ?
const nil:null = null
const undef:void = undefined
Funções
Nas funções podemos declarar tipo tanto nos parâmetros que está recebendo, quanto no seu retorno:
// ES6
const sum = (val1:number, val2:number):number => val1 + val2// ES5
function sum(val1:number, val2:number):number {
return val1 + val2
}
Arrays
podemos declarar de duas formas:
const arr:Array<number> = [1, 2, 3]
const arr:Array<string> = ['foo', 'bar']
// ou
const arr:number[] = [1, 2, 3]
const arr:string[] = ['foo', 'bar']
Caso seu Array possa receber dois tipos de valores mesclados, se declarado da forma acima, irá receber o seguinte erro:
const arr:Array<number> = ['foo', 1]
^ array literal. Has some incompatible type argument with array type Type argument `T` is incompatible:
Para estes caso, podemos declarar aceitando um tipo ou outro:
const arr:Array<string|number> = ['foo', 'bar', 1, 2, 3]
const arr:(string|number)[] = ['foo', 'bar', 1, 2, 3]
const arr:(string|number|boolean)[] = ['foo', 123, true]
Um outro detalhe que podemos fazer com a declaração de Array é definir o tipo de cada valor baseado na sua posição, dessa forma o Array irá respeitar o tamanho e o tipo definido. Isso é chamado de Tuplas.
Por exemplo:
const arr:[string, string, number] = ['foo', 'bar', 123]const arr:[string, string, number] = ['foo', 'bar']
^ array literal. Tuple arity mismatch. This tuple has 2 elements and cannot flow to the 3 elements of tuple type
Apenas para conhecimento: Existe um tipo chamado “any” que aceita qualquer tipo de valor, porém este deve ser utilizado somente em último caso, pois irá ignorar qualquer análise de tipo.
Objetos
Podemos declarar de forma genérica:
const obj:Object = {}
const obj:Object = {str: 'foo'}
Como também podemos declarar com uma espécie de “schema” onde cada propriedade deverá ser respeitada:
const obj:{str:string, num:number} = {str: 'foo', num: 123}
Caso o schema não seja respeitado, um erro será gerado:
const obj:{str:string, num:number} = {str: 'foo'}
^ object literal. This type is incompatible with object type
Caso você deseja que alguma propriedade seja opcional (pode existir ou não), e seja validada apenas quando existir, basta adicionar “?” após o nome da propriedade:
// OK
const obj:{str:string, num?:number} = {str: 'foo'}
const obj:{str:string, num?:number} = {str: 'foo', num: undefined}
const obj:{str:string, num?:number|null} = {str: 'foo', num: null}// NOK
const obj:{str:string, num?:number} = {str: 'foo', num: null}
É aí que você deve estar querendo me interromper e perguntar:
- Humm, bem legal isso! Mas teria alguma forma de ficar mais legível e reaproveitável meu código, de forma que eu consiga definir schemas customizados dos meus objetos e declará-los de forma que o Flow valide para mim as atribuições automaticamente ?
Muito bem pequeno gafanhoto! Eu sabia que você iria pensar nisso e preparei um exemplo. Isso o Flow chama de Alias:
// Definição do alias
type MyObject = {
str: string,
num: number
};// Atribuindo o alias à variável desejada
const myObj:MyObject = {
str: 'bar',
num: 123,
}
Um conceito importante é que o Flow entende que um objeto tipado, passa a ter suas propriedades “seladas”, ou seja:
const myObj:MyObject = {
str: 'bar'
}
myObj.num = 123 // funciona
myObj.newProp = 'foo bar' // não funciona
^ property `newProp`. Property not found in object type
Isso não funciona, pois quando tentamos atribuir “newProp” ao objeto, o flow verifica no Schema que esta propriedade não foi declarada e como ele trata os objetos de forma selada, nenhuma nova propriedade pode ser adicionada.
Porém observe que, se fizermos isso no momento da atribuição, irá funcionar:
const myObj:MyObject = {
str: 'bar',
num: 123,
newProp: 'intruso'
}
Então caso queiramos forçar exatamente e somente as propriedades que definirmos, precisamos declarar o schema adicionando “|” :
// Definição do alias
type MyObject = {|
str: string,
num: number
|};const myObj:MyObject = {
str: 'bar',
num: 123,
newProp: 'intruso'
^ property `newProp`. Property not found in object type
}
Está gostando da série ?
Clap, compartilhe, comente!
Feedback
Encontrou algum erro, sentiu falta de alguma explicação ou gostaria de ver algum exemplo que ainda não foi abordado, comente!