Flow: Fundamentos para React

Tudo para você adicionar tipos ao seus componentes

Image for post
Image for post
Uma dupla poderosa criada pelo Facebook!

Esse é um tutorial para desenvolvedores que usam React e estão querendo começar com FlowType.

Nós iremos cobrir um pouco das características básicas do Flow, para entendermos como usar FlowType com React.

Se você quer uma introdução completa em FlowType, você pode ler o meu outro artigo “Flow: Fundamentos para Desenvolvedores JavaScript”.

Se você tiver alguma dúvida ou comentário, pode entrar em contato no Twitter.

Versão utilizada: Flow v0.55

Instalação e Configuração

Sempre consulte a documentação oficial para uma compreensão mais detalhada: https://flow.org/en/

Para instalação, você pode dar uma olhada nessa página: https://flow.org/en/docs/install/

Você também pode brincar no REPL online: https://flow.org/try/

E pode configurar no seu editor preferido: https://flow.org/en/docs/editors/

Colocando tudo junto

Para começar, iremos ver como tradicionalmente trabalhamos com React e PropTypes. Nós temos um componente UserInput que espera props e gerencia algum estado local.

Tradicionalmente, usaríamos PropTypes para verificar dinamicamente se as props fornecidas são do tipo esperados.

import * as React from 'react'
import PropTypes from 'prop-types'
// não esqueça de instalar os tipos para `PropTypes` através do flow-typed para remover os erros ao usar flow! veja https://github.com/flowtype/flow-typed
class UserInput extends React.Component<*,*> {
propTypes = {
errors: PropTypes.array,
onSubmit: PropTypes.func.isRequired,
}
state = {
name: '',
email: '',
}
update = event => {
const { name, value } = event.currentTarget
this.setState({ [name]: value })
}
submit = () => {
const { name, email } = this.state
this.props.onSubmit(name, email)
}
render() {
const { name, email } = this.state
const { errors = [], onSubmit } = this.props
return (
<div>
{errors.length > 0 ? (
<div>{errors.map(error => <div>{error.msg}</div>)}</div>
) : null}
<input
className="name"
name="name"
value={name}
onChange={this.update}
/>
<input
className="email"
name="email"
value={email}
onChange={this.update}
/>
<button onClick={this.submit}>Sunmit</button>
</div>
)
}
}

Se você examinar mais de perto UserInput, você notará que nunca definimos uma forma de estado, apenas os tipos de props esperados. Agora vejamos como podemos reescrever o UserInput com FlowType.

Ao invés de ter que verificar o tipo dinâmicamente, podemos agora recorrer a um verificação estática, sem nunca ter executado o código.

type PropType = {
errors?: Array<{ msg: string }>,
onSubmit: (name: string, email: string) => void,
}
type StateType = {
name: string,
email: string,
}
class UserInputFlow extends React.Component<PropType, StateType> {
state = {
name: '',
email: '',
}
update = event => {
const { name, value } = event.currentTarget
this.setState({ [name]: value })
}
submit = () => {
const { name, email } = this.state
this.props.onSubmit(name, email)
}
render() {
const { name, email } = this.state
const { errors = [], onSubmit } = this.props
return (
<div>
{errors.length > 0 ? (
<div>{errors.map(error => <div>{error.msg}</div>)}</div>
) : null}
<input
className="name"
name="name"
value={name}
onChange={this.update}
/>
<input
className="email"
name="email"
value={email}
onChange={this.update}
/>
<button onClick={this.submit}>Sunmit</button>
</div>
)
}
}

E ao usar o nosso componente:

// Receberemos um alerta de erro!
const renderViewA = () => <UserInputFlow />

Você notará que o Flow vai reclamar que a propriedade onSubmit não pode ser encontrada.

Também é interessante notar que também não definimos error, maserror foi declarado como opcional, se você olhar a tipagem, então não é um erro aqui.

type PropType = {
errors?: Array<{ msg: string }>,
onSubmit: (name: string, email: string) => void,
}

A mensagem de erro é mais ou menos a seguinte:

const renderView = () => <UserInputFlow /> // Error!
^^^^^^^^^^^^^^^^^ React element `UserInputFlow`
class UserInputFlow extends React.Component<PropType, StateType> {
^^^^^^^^ property `onSubmit`. Property not found in
const renderView = () => <UserInputFlow /> // Error!
^^^^^^^^^^^^^^^^^ props of React element `UserInputFlow`

Nossa próxima etapa é definir a função onSubmit:

const onSubmitFnA = (name: string, email: string, status: string) => {
// Algum código...
}
// Alerta de erro!
const renderViewB = () => <UserInputFlow onSubmit={onSubmitFnA} />

Novamente, somos saudados com um erro aqui, como nós definimos onSubmit para esperar exatamente dois argumentos, sendo ambos de tipo string. Precisamos passar tudo corretamente:

const onSubmitFnB = (name: string, email: string) => {
// Algum código...
}
// Agora sim! Funcionando! :D
const renderViewC = () => <UserInputFlow onSubmit={onSubmitFnB} />

Ao passar a função corretamente definida através como props, finalmente nos livramos dos erros. Em seguida, você também pode adicionar errors e brincar com seus valores para ver o que acontece, se você estiver interessado.

Agora vamos voltar ao nosso componente e ver o que realmente definimos com mais detalhes.

class umExemplo extends React.Component<Props, State>`

Podemos ver que React.Component pode ser parametrizado com tipos de Props e State. Se você não tem nenhum estado, você pode omitir a definição.

class umExemplo extends React.Component<Props>

Se você não tem props, você pode usar um * para omitir sem ser específico ou usar void.

class umExemplo extends React.Component<*, State>

Ou:

class umExemplo extends React.Component<void, State>

Um exemplo completo:

class Foo extends React.Component<void, *> {
state = {
id: 1,
name: 'foobar',
}
render() {
return <div>{this.state.id} {this.state.name}</div>
}
}

Você também pode definir o State ou Props em linha, caso você não queira reutilizar a definição de tipo:

class Bar extends React.Component<void, {id: number, name: string}> {
state = {
id: 1,
name: 'foobar',
}
render() {
return <div>{this.state.id} {this.state.name}</div>
}
}

Você também pode tentar permitir que o Flow compreenda tudo usando *:

class umExemplo extends React.Component<*, *>

No nosso primeiro exemplo, UserInput, definimos a prop errors como opcional. Vamos mudar isso e torná-la obrigatória e adicionar um valor de erro padrão:

type PropTypeNonOptional = {
errors: Array<{ msg: string }>,
onSubmit: (name: string, email: string) => void,
}
class UserInputFlowWithDefault extends React.Component<
PropTypeNonOptional,
StateType,
> {
state = {
name: '',
email: '',
}
static defaultProps = {
errors: [],
}
update = event => {
const { name, value } = event.currentTarget
this.setState({ [name]: value })
}
submit = () => {
const { name, email } = this.state
this.props.onSubmit(name, email)
}
render() {
const { name, email } = this.state
const { errors = [], onSubmit } = this.props
return (
<div>
{errors.length > 0 ? (
<div>{errors.map(error => <div>{error.msg}</div>)}</div>
) : null}
<input
className="name"
name="name"
value={name}
onChange={this.update}
/>
<input
className="email"
name="email"
value={email}
onChange={this.update}
/>
<button onClick={this.submit}>Sunmit</button>
</div>
)
}
}
// Funcionando corretamente!
const renderViewD = () => <UserInputFlowWithDefault onSubmit={onSubmitFnB} />

Então, tudo o que precisamos fazer é definir o defaultProps e FlowType saberá que não precisamos definir uma prop de errors.

Criar tipagem para componentes funcionais e sem estados no React, gem geral, é equivalente a funções normais em Flow.

Por exemplo, podemos querer refatorar nossos campos de texto e oferecer um componente Input que retorna o elemento de texto necessário.

type InputProps = {
name: string,
value: string,
update: (name: string, email: string) => void
}
const Input = ({name, value, update} : InputProps) => {
return <input
className={name}
name={name}
value={value}
onChange={update}
/>
}
// Funcionando!
<Input name={'email'} value={'foo@bar.baz'} update={(name, value) => {}} />

Você também pode trabalhar com defaultProps no Flowtype, e ele não reclamará se deixar de fora uma propriedade obrigatória. Como um exercício, tente refatorar Input para trabalhar com defaultProps.

Vamos voltar para o nosso componente UserInput. O que também gostaríamos de fazer é tipar nosso método update.

Flow oferece SyntheticEvent<T>, especialmente para eventos. Como estamos usando um botão para ativar a atualização. Nós também podemos adicionar tipos usando: SyntheticEvent<HTMLButtonElement>.

class UserInputFlowTypedEvent extends React.Component<PropType, StateType> {
state = {
name: '',
email: '',
}
update = (event: SyntheticEvent<HTMLButtonElement>) => {
const { name, value } : HTMLButtonElement = event.currentTarget
this.setState({ [name]: value })
}
submit = () => {
const { name, email } = this.state
this.props.onSubmit(name, email)
}
render() {
const { name, email } = this.state
const { errors = [], onSubmit } = this.props
return (
<div>
{errors.length > 0 ? (
<div>{errors.map(error => <div>{error.msg}</div>)}</div>
) : null}
<input
className="name"
name="name"
value={name}
onChange={this.update}
/>
<input
className="email"
name="email"
value={email}
onChange={this.update}
/>
<button onClick={this.submit}>Sunmit</button>
</div>
)
}
}

Existem casos onde nós queremos adicionar tipos para o React.Children, por exemplo, quando usamos funções utilitárias do React.Children ou quando passamos um array de elementos.

Nós podemos usar React.Node para definir a tipagem de children, como no exemplo a seguir:

type RowsProps = {
children?: React.Node
}
const Rows = (props: RowsProps) => (
<div>
{props.children}
</div>
)
// Funciona!
const RowsView = () => <Rows>Test</Rows>

E se nós queremos apenas um elemento filho único?

type RowsPropsSingle = {
children: React.Element<any>
}
const RowSingle = (props: RowsPropsSingle) => (
<div>
{props.children}
</div>
)
// Alerta de erro!
const RowSingleViewA = () => <RowSingle />
// Alerta de erro!
const RowSingleViewB = () => <RowSingle><div>1</div><div>2</div></RowSingle>
// Funciona!
const RowSingleViewC = () => <RowSingle><div>Test</div></RowSingle> // Works!

E se declararmos uma série de elementos filho?

type RowsPropsArray = {
children: React.ChildrenArray<React.Element<any>>
}
const RowArray = (props: RowsPropsArray) => (
<div>
{/* ...algum código */}
</div>
)
// Alerta de erro!
const RowArrayViewA = () => <RowArray />
// Alerta de erro!
const RowArrayViewB = () => <RowArray>1</RowArray>
// Funciona!
const RowArrayViewC = () => <RowArray><span/><span/><span/></RowArray>

E se quisermos garantir que apenas strings sejam permitidas como children?

type RowsPropsStringOnly = {
children: React.ChildrenArray<string>
}
const RowsWithStringOnly = (props: RowsPropsStringOnly) => (
<div>
{/* ...algum código */}
</div>
)
// Alerta de erro!
const RowStringsOnylyViewA = () => <RowsWithStringOnly />
// Alerta de erro!
const RowStringsOnylyViewB = () => <RowsWithStringOnly><span>Test</span></RowsWithStringOnly>
// Funciona!
const RowStringsOnylyViewC = () => <RowsWithStringOnly>Test the example!</RowsWithStringOnly>

Conclusão

Por hora, isso é tudo! Você deve ter uma compreensão básica de como adicionar tipos para seus componentes React!

Flow tem uma parte da sua documentação exclusiva para React, você pode perder algumas horas lendo mais em: https://flow.org/en/docs/react

Compartilhe sua experiência ou dúvidas nos comentários! E você também pode entrar em contato através do Twitter!

Créditos

Written by

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store