Aguarde...

17 de janeiro de 2024

Os 2 Reacts

Os 2 Reacts

Suponha que eu queira exibir algo em sua tela. Se eu quiser exibir uma página da web como esta postagem do blog, um aplicativo da web interativo ou até mesmo um aplicativo nativo que você pode baixar de alguma loja de aplicativos, pelo menos dois dispositivos devem estar envolvidos.

Seu dispositivo e o meu.

Tudo começa com algum código e dados no meu dispositivo. Por exemplo, estou editando esta postagem do blog como um arquivo no meu laptop. Se você vê-lo na tela, ele já deve ter viajado do meu dispositivo para o seu. Em algum momento, em algum lugar, meu código e dados se transformaram em HTML e JavaScript instruindo seu dispositivo a exibir isso.

Então, como isso se relaciona com o React? React é um paradigma de programação de UI que me permite dividir o que exibir (uma postagem de blog, um formulário de inscrição ou até mesmo um aplicativo inteiro) em peças independentes chamadas componentes e compô-las como blocos de LEGO. Presumo que você já conheça e goste de componentes; verifique react.dev para uma introdução.

Componentes são código, e esse código precisa ser executado em algum lugar. Mas espere: em qual computador eles deveriam ser executados? Eles devem ser executados no seu computador? Ou no meu?

Vamos defender cada lado.


Primeiro, argumentarei que os componentes devem ser executados no seu computador.

Aqui está um pequeno botão de contador para demonstrar interatividade. Clique algumas vezes!

<Counter />

Você clicou em mim 0 vezes

Supondo que o código JavaScript deste componente já tenha sido carregado, o número aumentará. Observe que aumenta instantaneamente ao pressionar . Não há atraso. Não há necessidade de esperar pelo servidor. Não há necessidade de baixar nenhum dado adicional.

Isso é possível porque o código deste componente está sendo executado no seu computador:

import { useState } from "react"; export function Counter() {  const [count, setCount] = useState(0);  return (    <button      className="dark:color-white rounded-lg bg-purple-700 px-2 py-1 font-sans font-semibold text-white focus:ring active:bg-purple-600"      onClick={() => setCount(count + 1)}    >      You clicked me {count} times    </button>  );}

Aqui countestá um pedaço do estado do cliente – um pedaço de informação na memória do seu computador que é atualizado toda vez que você pressiona esse botão. Não sei quantas vezes você vai pressionar o botão , então não posso prever e preparar todas as saídas possíveis no meu computador. O máximo que ouso preparar no meu computador é a saída inicial da renderização (“Você clicou em mim 0 vezes”) e enviá-la como HTML. Mas a partir desse momento, seu computador teve que assumir a execução desse código.

Você poderia argumentar que ainda não é necessário executar esse código no seu computador. Talvez eu pudesse executá-lo no meu servidor? Sempre que você pressionar o botão, seu computador poderá solicitar ao meu servidor a próxima saída de renderização. Não era assim que os sites funcionavam antes de todas aquelas estruturas JavaScript do lado do cliente?

Solicitar ao servidor uma nova UI funciona bem quando o usuário espera um pequeno atraso – por exemplo, ao clicar em um link. Quando o usuário souber que está navegando para algum lugar diferente no seu aplicativo, ele esperará. No entanto, qualquer manipulação direta (como arrastar um controle deslizante, alternar uma guia, digitar em um compositor de postagem, clicar em um botão curtir, deslizar um cartão, passar o mouse sobre um menu, arrastar um gráfico e assim por diante) pareceria quebrada se isso acontecesse. Não forneça pelo menos algum feedback instantâneo de forma confiável.

Este princípio não é estritamente técnico – é uma intuição da vida cotidiana. Por exemplo, você não esperaria que um botão de elevador o levasse ao próximo andar num instante. Mas quando você empurra a maçaneta de uma porta, você espera que ela siga diretamente o movimento da sua mão, ou ela ficará presa. Na verdade, mesmo com um botão de elevador você esperaria pelo menos algum feedback instantâneo: ele deveria ceder à pressão da sua mão. Então deve acender para reconhecer sua imprensa.

Ao construir uma interface de usuário, você precisa ser capaz de responder a pelo menos algumas interações com baixa latência garantida e sem viagens de ida e volta de rede.

Você deve ter visto o modelo mental React sendo descrito como uma espécie de equação: UI é uma função de state , ou UI = f(state). Isso não significa que o código da UI deva ser literalmente uma única função que toma o estado como argumento; significa apenas que o estado atual determina a IU. Quando o estado muda, a IU precisa ser recalculada. Como o estado “vive” no seu computador, o código para calcular a UI (seus componentes) também deve ser executado no seu computador.

Ou assim continua esse argumento.


A seguir, argumentarei o contrário – que os componentes devem ser executados no meu computador.

Aqui está um cartão de visualização para uma postagem diferente deste blog:

<PostPreview slug="a-chain-reaction" />

Como um componente desta página sabe o número de palavras dessa página?

Se você verificar a guia Rede, não verá solicitações extras. Não estou baixando aquela postagem inteira do GitHub apenas para contar o número de palavras nela. Também não estou incorporando o conteúdo dessa postagem do blog nesta página. Não estou chamando nenhuma API para contar as palavras. E com certeza não contei todas essas palavras sozinho.

Então, como esse componente funciona?

import { readFile } from "fs/promises";import matter from "gray-matter"; export async function PostPreview({ slug }) {  const fileContent = await readFile("./public/" + slug + "/index.md", "utf8");  const { data, content } = matter(fileContent);  const wordCount = content.split(" ").filter(Boolean).length;   return (    <section className="rounded-md bg-black/5 p-2">      <h5 className="font-bold">        <a href={"/" + slug} target="_blank">          {data.title}        </a>      </h5>      <i>{wordCount} words</i>    </section>  );}

Este componente é executado no meu computador. Quando quero ler um arquivo, leio um arquivo com extensão fs.readFile. Quando quero analisar seu cabeçalho Markdown, eu o analiso com gray-matter. Quando quero contar as palavras, divido o texto e as conto. Não há nada extra que eu precise fazer porque meu código é executado exatamente onde estão os dados .

Suponha que eu queira listar todas as postagens do meu blog junto com a contagem de palavras.

Tudo que eu precisava fazer era renderizar uma pasta <PostPreview />para cada postagem:

import { readdir } from "fs/promises";import { PostPreview } from "./post-preview"; export async function PostList() {  const entries = await readdir("./public/", { withFileTypes: true });  const dirs = entries.filter(entry => entry.isDirectory());  return (    <div className="mb-4 flex h-72 flex-col gap-2 overflow-scroll font-sans">      {dirs.map(dir => (        <PostPreview key={dir.name} slug={dir.name} />      ))}    </div>  );}

Nenhum desses códigos precisava ser executado no seu computador — e de fato não poderia, porque o seu computador não tem meus arquivos. Vamos verificar quando este código foi executado:

<p className="text-purple-500 font-bold">  {new Date().toString()}</p>

Sexta-feira, 05 de janeiro de 2024 00:50:25 GMT + 0000 (Tempo Universal Coordenado)

Aha, foi exatamente quando implantei meu blog pela última vez em minha hospedagem estática na web! Meus componentes foram executados durante o processo de construção para que tivessem acesso total às minhas postagens.

A execução de meus componentes perto de sua fonte de dados permite que eles leiam seus próprios dados e os pré-processem antes de enviar qualquer informação para seu dispositivo.

No momento em que você carregou esta página, não havia mais <PostList>e não mais <PostPreview>, não fileContente não dirs, não fse não gray-matter. Em vez disso, havia apenas um <div>com alguns <section>s com <a>s e <i>s dentro de cada um deles. Seu dispositivo recebeu apenas a IU que realmente precisa exibir (os títulos das postagens renderizadas, URLs dos links e contagens de palavras das postagens), em vez dos dados brutos completos que seus componentes usaram para calcular essa IU (as postagens reais).

Com este modelo mental, a UI é uma função dos dados do servidor , ou UI = f(data). Esses dados existem apenas no meu dispositivo, então é onde os componentes devem ser executados.

Ou assim diz o argumento.


A UI é feita de componentes, mas defendemos duas visões muito diferentes:

  • UI = f(state)onde stateestá do lado do cliente e fé executado no cliente. Essa abordagem permite escrever componentes interativos instantaneamente como <Counter />. (Aqui, tambémf pode ser executado no servidor com o estado inicial para gerar HTML.)
  • UI = f(data)onde dataestá do lado do servidor e fé executado apenas no servidor. Essa abordagem permite escrever componentes de processamento de dados como <PostPreview />. (Aqui, fé executado categoricamente apenas no servidor. O tempo de construção conta como “servidor”.)

Se deixarmos de lado o viés da familiaridade, ambas as abordagens são convincentes no que fazem de melhor. Infelizmente, estas visões parecem mutuamente incompatíveis.

Se quisermos permitir interatividade instantânea como a necessária <Counter />temos que executar componentes no cliente. Mas componentes como <PostPreview />não podem ser executados no cliente, em princípio, porque usam APIs somente de servidor, como readFile. (Esse é o ponto principal! Caso contrário, podemos executá-los no cliente.)

Ok, e se executarmos todos os componentes no servidor? Mas no servidor, componentes como <Counter />só podem renderizar seu estado inicial . O servidor não conhece seu estado atual , e passar esse estado entre o servidor e o cliente é muito lento (a menos que seja pequeno como uma URL) e nem sempre é possível (por exemplo, o código do servidor do meu blog só é executado na implantação, então você pode ‘ t “passar” coisas para ele).

Novamente, parece que temos que escolher entre dois Reacts diferentes:

  • O paradigma “cliente” UI = f(state)que nos permite escrever <Counter />.
  • O paradigma do “servidor” UI = f(data)que nos permite escrever arquivos <PostPreview />.

Mas, na prática, a verdadeira “fórmula” está mais próxima de UI = f(data, state). Se você não tivesse dataou não state, generalizaria para esses casos. Mas, idealmente, eu preferiria que meu paradigma de programação fosse capaz de lidar com ambos os casos sem ter que escolher outra abstração, e sei que pelo menos alguns de vocês também gostariam disso.

O problema a resolver, então, é como dividir nosso “ f” em dois ambientes de programação muito diferentes. É mesmo possível? Lembre-se de que não estamos falando de alguma função real chamada f– aqui frepresenta todos os nossos componentes.

Existe alguma maneira de dividir os componentes entre o seu computador e o meu de uma forma que preserve o que há de bom no React? Poderíamos combinar e aninhar componentes de dois ambientes diferentes? Como isso funcionaria?

Como isso deveria funcionar?

Pense um pouco e da próxima vez compararemos nossas anotações.

Postado em BlogTags:
Escreva um comentário