React-cache, time slicing e busca com uma API síncrona

Marvin Frachet Blocked Desbloquear Seguir Seguindo 11 de janeiro

Bem, este ano parece ser o ano do React. Você provavelmente já ouviu falar do novo recurso matador que vem com o 16.7 – Hooks. Você provavelmente já ouviu falar sobre outras coisas boas e legais como o Time Slicing ou até mesmo o Suspense.

Este artigo não tem como objetivo descrever como usar alguns dos novos recursos, mas sim como eles podem ter sido construídos. Apenas para entender o que estamos jogando.

Também está escrito da maneira que descobri o recurso. Provavelmente não é o modo como foi pensado, mas é assim que recebi os pontos.

O que você encontrará ao ler:

  • JavaScript assíncrono e o loop de eventos
  • Efeitos algébricos em React, com exemplo
  • Fases de Fibra e Reação

Por que escrevi este post?

O que me fez querer escrever este post foi esse recurso especial e experimental que permite o uso de operações assíncronas usando uma API síncrona :

https://codesandbox.io/s/6y0jpl802k

const bulbasaur = ApiResource.read() ?… Qual o? Síncrono ?!

A biblioteca do cache de reações cria a capacidade de usar operações assíncronas com uma API síncrona. Este é o recurso que me fez querer aprender como o React está funcionando sob o capô. Aqui está uma apresentação apresentada por Dan Abramov e Andrew Clark nesta biblioteca:

Dan Abramov – React Island 2018- Suspense…;)

Como isso é possível? Como podemos obter alguns dados remotos usando chamadas síncronas?

Vamos nos aprofundar neste exemplo e tentar entender como o react-cache implementa tal funcionalidade e descobre como ela pode funcionar. Esta história começa com a arquitetura de fibra .

Controlando operações JavaScript

A arquitetura de fibra permite que o React assuma o controle das execuções de tarefas. Foi construído para resolver vários problemas que o React sofreu. Aqui estão os dois que me chamaram a atenção:

  • priorizando eventos específicos, como a entrada do usuário sobre a busca de dados
  • divisão assíncrona React computation para manter a disponibilidade do thread principal e evitar bloqueá-lo durante longos processos de renderização

Tudo o que aciona uma mudança de estado – não apenas com o React – dentro de um aplicativo JavaScript é devido a operações assíncronas. Estes incluem setTimeout , fetch e ouvintes para eventos.

As operações assíncronas são gerenciadas por meio de vários conceitos básicos de JavaScript:

  • tarefas (micro, macro, render etc…)
  • loop de eventos
  • callstack

Se você não está familiarizado com esses conceitos, sugiro que dê uma olhada neste vídeo de Jake Archibald :

Jake Archibald no circuito de eventos

Graças à fibra, as entradas do usuário são resolvidas antes de outras operações assíncronas, como buscar chamadas.

Como isso é possível?

Bem, a palestra de Archibald acima foi a primeira pedra pavimentada do meu próprio caminho de aprender sobre como funciona o loop de eventos. Ele diz que as micro tarefas – geradas por meio da API do Promise, por exemplo – são executadas e liberadas antes da próxima tarefa de macro. Esse processo usa métodos baseados em retorno de chamada, como setTimeout .

Então, se você se lembrar da minha comparação de “entrada de usuário versus busca de dados”, como a equipe fez fetch resoluções depois das resoluções do onChange ?

Resoluções de retorno de chamada e promessa

Nenhum desses conceitos se encaixa na mesma especificação, WhatWG / HTML5 / Ecma-262 , e são fornecidos em locais diferentes, como o navegador ou os mecanismos JS.

Quero dizer, como devemos resolver uma Promise depois de um setTimeout ?

Isso soou absolutamente louco para mim e foi muito difícil ter uma ideia de como isso poderia estar funcionando. O fato é que ocorre em um nível mais alto.

Mais tarde, assisti a incrível conversa de Brandon Dail no React Rally. Isso apresenta os novos recursos de Faturamento de Tempo e Suspense que foram enviados graças à arquitetura de fibra React:

Brandon Dail – efeitos algébricos, fibras, coroutines…

De acordo com o Dail, o fiber é igual ao callstack do JavaScript em que cada item da pilha é chamado de fibra . É diferente do callstack que depende de quadros que representam funções (+ metadados). Pelo contrário, uma fibra representa um componente (+ metadados) . Vamos ver uma fibra como uma caixa enorme em torno de um componente que sabe tudo sobre isso.

Existe uma diferença importante entre esses dois conceitos.

Na primeira mão, o callstack é uma funcionalidade que foi construída sobre a parte nativa que conduz o Código JavaScript . Destina-se a empilhar todas as chamadas de função JavaScript e executá-las por conta própria. Cada vez que chamamos uma função, ela é adicionada à pilha. Sem o callstack, não poderíamos ter registros de pilha de erros claros e detalhados. E como o callstack não pode ser acessado por um código JavaScript, é realmente difícil e até mesmo impossível assumir o controle sobre ele.

Por outro lado, as fibras – como uma pilha de fibra – representam o mesmo conceito, mas são construídas em código JavaScript. A menor unidade não é uma função, mas um componente . Na verdade, ele é executado em um universo JavaScript.

O fato de a arquitetura de fibra ser completamente construída em JavaScript significa que podemos usá-la, acessá-la e modificá-la. Podemos trabalhar com isso usando JavaScript padrão.

O que me levou à direção errada foi que achei que o React estava usando uma solução alternativa para interromper a maneira como o JavaScript está funcionando. Não é o caso . As fibras são simplesmente objetos JavaScript que possuem informações sobre os componentes do React e que podem interagir com seus ciclos de vida. Ele só pode atuar em funcionalidades internas React.

A ideia não é redefinir como JavaScript deve estar trabalhando, como dizer que fetch resolução Microtask deve ser executado antes de tarefas de retorno de chamada. É mais sobre quais métodos do React devem ser chamados ou não em um contexto específico, como interromper as diferentes chamadas do método de ciclo de vida.

Ei, espere! Você diz que as fibras podem controlar absolutamente tudo em um React App? Mas como um componente pode dizer ao React para parar de fazer qualquer coisa?

Efeitos algébricos, sim, mas em JavaScript por favor

O React é capaz de controlar componentes e saber se um componente está sendo executado, graças à arquitetura de fibra. O que está faltando agora é uma maneira de dizer ao React que algo mudou para um componente específico, então ele lidará com essa mudança.

É aqui que os efeitos algébricos entram no jogo.

Efeitos algébricos não são algo que existe em JavaScript. Vou tentar explicar o que eles são com uma explicação de nível superior.

Efeitos algébricos são um conceito que permite enviar alguma informação em algum lugar, um pouco como um despachante. A idéia é chamar uma função específica que interrompa a função atualmente em execução em uma posição precisa para permitir que uma função pai manipule uma computação. Quando o cálculo pai terminar, ele poderá retomar o programa para a posição inicial em que as informações foram enviadas.

Algumas linguagens como OCaml ou Eff se beneficiam desses recursos nativamente. Essa é uma abstração realmente interessante, pois os detalhes da implementação dependem apenas do pai:

Pseudo JavaScript onde “propagar”, “withEffects” e “handle” existiriam

Não seria incrível ter esse recurso em JavaScript?

A equipe React criou uma abordagem semelhante em um contexto React que lida com o bloco try/catch JavaScript. De acordo com Dail, é o conceito disponível mais próximo em JavaScript.

Jogar algo permite enviar informações para um pai, em algum lugar. O primeiro pai que pega a informação é capaz de lidar com isso e fazer cálculos sobre ela.

Um exemplo é melhor que mil palavras

Imagine o seguinte código que tenta buscar o Bulbasaur usando uma API síncrona :

Buscando um recurso Pokeapi usando uma API síncrona

Essa parte do código pode ser estranha, já que não é muito comum buscar dados usando uma API síncrona. Vamos pular dentro da implementação da função customFetch :

Detalhes da implementação do customFetch

Oh espere! Isso absolutamente não parece uma busca! Eu não entendo o que esta função pretende fazer…

Bem, imagine algo em torno do componente , digamos uma fibra que se pareça com:

Uma fibra HOC, como uma fibra “em volta” de um componente

Tire algum tempo para ler o código.

Agora, vamos pular para a implementação customFetch :

Detalhes da implementação do fetchWithCache

A parte importante nos trechos anteriores é o bloco try/catch .

Vamos resumir o que está acontecendo com esses diferentes pedaços de código:

  • O componente Pokemon chama o método customFetch .
  • O método customFetch tenta ler seu cache interno, mas está vazio. Por isso, lança algo / algures – efeitos algébricos.
  • O pai da fiber captura essa informação, lida com ela e busca os dados. Em seguida, ele preenche o cache do customFetch com os dados.
  • Uma nova renderização ocorre em Component(args) e, agora, o cache customFetch está cheio. Os dados estão agora disponíveis no componente usando uma API síncrona.

Dê uma olhada nos detalhes da implementação do react-cache react e verifique os diferentes lançamentos.

Algo pode ter chamado sua atenção durante este processo: render foi chamado duas vezes. Um para lançar o erro – pausando o componente – e outro para obter os dados – retomando o componente. Não há problema se o React acionar várias chamadas de render , já que é apenas uma função pura – não tem efeitos colaterais por si só.

Espere o que? render não tem efeitos colaterais? E quanto ao DOM?

Reagir fases

Se você está trabalhando com o React há muito tempo, pode ter ouvido falar que não é uma boa prática renderizar novamente várias vezes. Antes da arquitetura de fibra, toda vez que estávamos chamando a função de renderização, o React fazia alguns cálculos internos e então modificava o DOM de acordo. Por exemplo, isso aconteceu ao chamar a função render através de setState . O processo foi embutido:

setState ? render ? comparar Virtual Nodes ? atualizar os nós DOM

Lidando com fibra, o processo é um pouco diferente. Ele introduziu um conceito de fila e lotes que permite modificações DOM de alto desempenho.

A ideia é bastante simples. Assumimos que as telas podem rodar ~ 60 quadros por segundo. A partir dessa suposição, e usando as funções JavaScript disponíveis, é possível fazer alguns cálculos e modificações no DOM somente a cada ~ 16.7ms. Com fibra, o React pode enfileirar várias modificações e enviá-las cerca de 60 vezes por segundo.

Esse tipo de modificação permitiu que o React se dividisse em três fases com suas próprias vantagens e particularidades:

Dan Abramov sobre as fases de Reacção

  • A fase de renderização é pura e determinista. Não tem efeitos colaterais e as diferentes funções que compõem podem ser chamadas várias vezes. A fase de renderização é interrompível – não é a função de render que está no modo de pausa, mas a fase inteira
  • A frase pré-commit visa fornecer acesso ao estado DOM real, como as posições da barra de rolagem, no modo de leitura.
  • A fase de confirmação realmente modifica o DOM e não é interrompível . Reagir não pode parar durante essa fase.

Este conjunto de três fases introduziu os recursos de fatiamento de tempo. O React pode pausar durante a fase de renderização, entre duas chamadas de função do componente, e para retomar essa fase quando necessário.

Em fibra, render apenas tem como objetivo obter a última representação disponível de um componente com base em seu estado interno para fazer algumas comparações e saber se o React precisa alterar o DOM ou não. Se uma modificação de confirmação for necessária, ela adicionará a modificação a uma fila de "trabalho em andamento".

A equipe do React fez grandes melhorias de desempenho graças ao React Concurrent (Time Slicing + Suspense) e à arquitetura de fibra. Eles criaram soluções alternativas para combater problemas de navegadores diferentes, como priorização de eventos e simultaneidade.

Se dermos um passo atrás, não é isso que eles mostraram? A priorização parece ser o novo desafio para as estruturas de navegador e front-end.

Outras equipes também estão trabalhando para melhorar o estado atual da arte e até mesmo propor APIs futuras. Esta é a opinião do Google: