React: Um novo padrão para aplicações

React Conf 2018 foi mais do que uma simples conferência

Image for post
Image for post

TL;DR: React Concorrente e Hooks não são simplesmente novas maneiras de fazer as mesmas coisas. Eles representam uma nova e forte opinião de como aplicações devem se comportar por padrão!

⚠️ Atenção: Esse artigo é uma opinião particular e discute APIs futuras e instáveis do React, que você pode optar em utilizar. Você não precisa aprender ou adotar elas, ainda! A documentação e blog do React continua sendo a principal maneira de encontrar recomendações oficiais.

React Conf 2018 não foi apenas mais uma conferência de desenvolvedores: As apresentações falando sobre React Hooks, Modo Concorrente e o DevTools Profiler pareciam mais um lançamento de produtos. Imaginando que isso representa o trabalho de uma equipe de 7 pessoas, para uma audiência de aproximadamente 1.25 milhões de desenvolvedores React, crescendo a 70% o ano, elas precisam ser muito bem apresentadas:

Para exagerar um pouco, foi mais ou menos assim que a apresentação da Sophie Alpert se pareceu:

Mas esse não é mais um artigo de fã do React.

Então, nós vamos conversar sobre: o que faltou ao final dessas apresentações?.

O que as pessoas NÃO estão conversando o suficiente

Houve revisões positivas e negativas dos novos recursos (embora eu ache que o DevTools Profiler foi bem elogiado!) E só você pode decidir se gosta deles.

O que eu senti que estava faltando nas palestras e nos debates subsequentes foi uma discussão explícita de como o comportamento padrão irá mudar. React Síncrono para React Concorrente, ou de Componentes de Classes para Componentes de Função com Hooks. Vamos discutir os novos comportamentos padrão e então terminaremos com porque eu acho que eles são importantes de se discutir mais a fundo.

Concorrente por padrão: Suspense e Time Slicing

“Concorrente” é o novo termo que a equipe do React está adotando, fique tranquilo se isso não significa nada para você até o momento. Na minha interpretação, ele descreve o objetivo da equipe do React em ajudar você a escrever aplicativos de alto desempenho por padrão. Caso você não tenha assistido, eu recomendo ver a apresentação na React Conf 2018 “Concurrent Rendering”.

Como o React não afirma que resolve todos os problemas de performance, agora eles estão disponibilizando duas soluções nativas para 2 problemas comuns:

  • Estados não-intencionais (Unintentional States)
  • Renderização e confirmação ininterrupta (Uninterruptible Render-and-Commit)

Para deixar claro: Esses termos NÃO são oficiais da equipe do React, estou usando as minhas próprias palavras para “explicar” os conceitos.

Padrões são apenas isso, padrões. Você pode, claro, não utilizar esses padrões e escrever bastante código com estado interno e imperativo, provavelmente com bugs, ou importar uma biblioteca como “react-loadable” onde a solução já foi escrita para você.

Estados não-intencionais

É o padrão onde cada componente não tem conhecimento do estado de seus filhos e onde cada componente é responsável por sua própria renderização. Esse padrão geralmente resulta em spinners em cascata, embora os spinners não sejam o problema.

Normalmente, para corrigir esse tipo de problema, nós “levantamos o estado” (lift state up) — a única maneira de permitir que o App controle o carregamento e a exibição de seus componentes filhos.

O React Concorrente muda esse comportamento padrão para:

Minha dica para entender React Concorrente é: Esqueça as pequenas coisas, como a “limpeza” do código ou a preferência entre classes e funções.

Perceba como o comportamento padrão mudou. Para a mesma estrutura de código, nós temos um comportamento bem diferente.

Os estados não-intencionais simplesmente foram embora. Se eu realmente quiser mostrar múltiplos “spinners” e carregar os dados da minha API de uma maneira descontrolada, eu vou precisar escrever mais código, fazer um trabalho extra, com intenção de fazer!

Você pode ver um exemplo de React Suspense nesse link do CodeSandbox.

Renderização e confirmação ininterrupta

No padrão atual, os processamentos e renderizações de componentes e função podem bloquear a thread principal se forem muito “caros”. Até que eles terminem e sejam renderizados no DOM as interações de usuário podem não receber resposta, resultando em “janky” UIs.

Neste exemplo, nosso render se torna caro em virtude de ter uma função super custosa dentro dele. Quando eu digito meu primeiro caractere no campo, o re-render demora muito e assim, a thread é bloqueada para o próximo caractere que eu tento digitar e o aplicativo não responde.

Para corrigir isso, você poderia dividir o componente e fazer o trabalho caro de forma assíncrona, adicionando callbacks ou Promise. Em um futuro próximo, com o Modo Concorrente e a API scheduler.scheduleCallback, você pode simplesmente declarar que essa é uma atualização de baixa prioridade:

E agora eu posso digitar o que estiver em meu coração que o cálculo de dados caro continua no mesmo componente. Isso funciona devido à metodologia de fila de atualização múltipla do React chamada Time Slicing. Os comentários no código-fonte são altamente recomendados (embora não obrigatórios) para entender isso.

Como Andrew Clark explicou em seu keynote, o React Concorrente pode renderizar parcialmente uma árvore sem comprometer o resultado, quebrando o paradigma de commit-and-render que costumávamos ter. Também não bloqueia a thread principal. Na verdade, é assim que funciona por padrão.

Mas não para por aí — a atribuição de prioridades pode ser muito mais invisível no futuro, por exemplo, com um atributo hidden={true} podemos definir uma prioridade de renderização mais baixa possível para um componente. Isso permite que você inicie o pré-processamento e a busca de dados para a página sem se comprometer com o DOM e tudo sendo cacheado.

Você pode tentar comparar o modo Síncrono x Concorrente em um aplicativo Time Slicing no CodeSandbox.

Ambas as soluções são dois lados da mesma moeda. A divisão da confirmação da renderização (commit-and-render) é fundamental para o funcionamento do Time Slicing e do Suspense, e esse novo padrão é o que você recebe no Modo Concorrente.

Modo Imediato por padrão: React Hooks

Hooks são a nova proposta da equipe do React para resolver os problemas com o “wrapper hell”, componentes inflados e classes confusas. Vale a pena ler a proposta na documentação oficial e assistir a palestra de abertura do React Conf 2018, chamada de React Today and Tomorrow, não há lugar melhor para aprender sobre essas funcionalidades tão novas.

É difícil resumir uma proposta tão abrangente, mas vamos nos concentrar no que é o novo padrão dos Hooks.

Primeiro, vamos criar uma perspectiva.

O JSX nos permite usar APIs imperativas do DOM, como:

E expressá-las declarativamente:

Utilizando outra perspectiva, talvez menos familiar, podemos imaginar essas APIs com o conceito de programação de jogos, o modo retido (retained mode) e o [modo imediato](https://en.wikipedia.org/wiki/Immediate_mode_(computer_graphics?) (immediate mode).

Para simplificar drasticamente, o “modo retido” é como o DOM funciona — há um conceito de objetos persistentes que você modifica de forma incremental. O “modo imediato” é como o React quer que você trate suas funções entre renderizações, declarando seu JSX como se estivesse sendo renderizado novamente a cada vez. No entanto, para o restante de seus componentes de classe, o React ainda espera que você codifique imperativamente no “modo retido” com os métodos de estado e ciclo de vida.

Essa linguagem “modo imediato vs retido” não parece que está mais em moda, mas quando o React foi introduzido pela primeira vez, Pete Hunt deu uma excelente palestra sobre como o Virtual DOM do React o ajuda a escrever como se estivesse no Modo Imediato, enquanto na verdade, estava interagindo com as APIs do DOM em Modo Retido. Visões similares de James Long, Sebastian Markbage e Andre Staltz, nesse período, também apoiam esse ponto de vista.

O modo imediato é fundamentalmente mais declarativo e, portanto, mais fácil de raciocinar, mas o modo retido é mais eficiente e é fundamentalmente o funcionamento das APIs do DOM. O reconciliador do React liga os dois modos e nos permite escrever como se estivéssemos no modo imediato na função de renderização.

Com essa perspectiva, podemos ver React Hooks criando o mesmo contexto, mas para as funções restantes de um componente React (ciclo de vida, etc).

Vamos adaptar um exemplo da documentação do Hooks:

Aqui o componente modifica o título do documento com seu nome, na montagem e desmontagem. No entanto, temos um erro: Se this.props.name mudar de foo para bar enquanto o componente ainda estiver montado, o título do documento estará fora de sincronização! (Ainda vai mostrar foo). Você também precisa implementar componentDidUpdate com uma cópia do seu código componentDidMount para que ele funcione como esperado.

Isso ocorre porque o padrão da API para efeitos em Componentes de Classe é análogo ao “Modo Retido”, onde você executa código imperativo dentro dos métodos genéricos de ciclo de vida que o React expõe a você. O ônus está em você lembrar de encadear a sequência correta de métodos de ciclo de vida para manter seus efeitos colaterais atualizados.

Compare o mesmo código com Hooks em componentes de função:

Como os efeitos são executados em cada atualização, você nunca se esquece de implementar um equivalente componentDidUpdate, é assim que funciona por padrão!

Na verdade, você precisa adicionar um array vazio para ignorar os efeitos, se realmente não quiser que o efeito seja executado novamente após a primeira atualização.

Podemos imaginar o seguinte argumento para como o estado funciona em componentes de classe e função:

  • Em classes: Você define this.state dentro do constructor e referência ele dentro do componente (modo retido)
  • Em funções: Você desestrutura de useState em toda atualização (modo imediato)

Esse é um novo padrão no React, análogo a permitir que você execute ciclos de vida com estado e com efeitos colaterais no “Modo Imediato” da mesma forma que o React faz para sua função de renderização. Uma melhor composição, evitando o “wrapper hell” e os tipos no pé que classes criam, é um bom resultado deste novo modelo. Vale lembra que, tudo isso são minhas afirmações pessoais e não da equipe do React.

Na verdade, a analogia entre Hooks e JSX é tão próxima que na documentação dos Hooks (onde a ordem de chamada é implicitamente importante, mas na prática não é um problema) tem uma analogia direta com JSX (onde a ordem dos componentes é implicitamente importante, mas na prática não é um problema).

Conclusão: A Floresta das Árvores

Eu sempre suspeitei que existe uma hierarquia de preocupações na hierarquia de Maslow, quando se trata de discutir o design da API, algo como:

Na parte inferior está o maior fato para bikeshedding (que, a propósito, não significa que essas reclamações sejam inválidas porque um design ergonômico para algo com o qual interagimos frequentemente é de fato importante). Eles são facilmente alterados e, portanto, menos permanentes. Também oferecem uma ampla gama de alternativas válidas.

O que quer que esteja no nível mais alto da pirâmide certamente está em debate (na verdade, tudo isso é — muito filosófico!). Ter uma “filosofia” do design da API parece um pouco pesado e grandioso, embora seja pouco especificado. Ter “motivações” no nível superior da pirâmide enfoca muito os problemas quando o objetivo é realmente o design da solução.

Eu sugiro que, o que realmente nos importa, e o que determina o sucesso ou fracasso de longo prazo da API, é a opinião que ela incorpora.

Todas as boas bibliotecas têm uma opinião: mesmo as que se dizem “não opinativas”, isso só significa que elas não tem uma opinião sobre todo o restante do qual elas tem uma opinião formada. Uma tautologia implícita. As opiniões são boas, especialmente as pequenas opiniões bem executadas.

As novas opiniões do React são de que os dois novos padrões, Concorrente por padrão e Modo Imediato por padrão, são melhores meios de escrever aplicativos melhores.

Ao considerar se você gosta das novas APIs ou não, comece por aí.

⭐️ 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