React: Suporta renderização de servidor assíncrona (aguardando dados antes de renderizar)

Criado em 24 jun. 2014  ·  139Comentários  ·  Fonte: facebook/react

Isso facilitaria seriamente o processo de construção de algo isomórfico se componentWillMount pudesse retornar uma promessa e essa reação atrasaria a renderização até que essa promessa fosse resolvida. Eu vi tentativas de fazer algo assim no react-router e no roteador, no entanto, atribuir essa responsabilidade a cada componente em vez de um módulo de roteador faria mais sentido para mim.

Component API Server Rendering Backlog Feature Request

Comentários muito úteis

Alguns meses atrás eu dei uma palestra no JSConf Iceland que descreve os próximos recursos de renderização assíncrona no React (veja a segunda parte): https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react -16.html. Trata-se da busca de dados do lado do cliente.

Agora @acdlite deu uma palestra sobre como os mesmos conceitos podem ser aplicados para habilitar a espera assíncrona de dados no servidor em componentes React, e progressivamente liberando a marcação quando ela estiver pronta: https://www.youtube.com/watch?v= z-6JC0_cOns

Espero que você goste de assistir a essas palestras! Acho que em um ano ou mais poderemos fechar essa questão e ter uma estratégia oficial para isso.

Todos 139 comentários

A principal razão (eu acredito) que isso ainda não existe é que no lado do cliente, você basicamente sempre quer mostrar algum tipo de indicador de carregamento em vez de adiar a renderização. (Isso também tornaria o código significativamente mais complexo, mas provavelmente podemos lidar com isso.)

Existem 2 casos que acho difícil resolver sem isso:

  • No lado do servidor, se você deseja buscar dados antes da renderização, não pode delegar a recuperação de dados aos componentes, pois não tem as informações de qual componente será renderizado
  • No lado do cliente, na primeira vez que você montar seu aplicativo depois de receber html pré-renderizado, mesmo se tiver algum tipo de cache de dados recuperados no servidor, convém usar o método assíncrono para recuperar esses dados, e isso impediria a reação de reutilizar o html.

react-async resolve esses problemas com fibras e cache. Isso faz o truque, mas no meu ponto de vista, essas são apenas soluções _hackish_ para resolver um problema que só pode ser resolvido no núcleo.

Cor-me desinformado sobre este assunto, @fdecampredon diz que componentWillMount é assíncrono e você não retorna nada imediatamente, o que o React deve renderizar até que haja, nada? Em caso afirmativo, por que não retornar nada na renderização se ainda não houver dados? (Sim, eu recebo do lado do servidor) Além disso, o que deve acontecer se os adereços mudarem antes de componentWillMount disparar?

Pessoalmente, parece errado despachar solicitações assíncronas durante componentWillMount menos que o componente seja realmente uma caixa preta isolada e também implemente um indicador de carregamento. Pelo que entendi, os componentes React não devem ser confundidos com instâncias OOP mais convencionais. Na melhor das hipóteses, um componente React é uma ferramenta para visualizar os dados em props, se for interativo, possivelmente também state. É uma visão, não uma visão e modelo.

Para meus ouvidos, isso soa como o problema, os componentes React não devem ser os que despacham as solicitações assíncronas, você busca todos os dados e quando esses dados estão prontos, só então você chama React.renderComponent . Mesma solução do lado do cliente e do lado do servidor. Você também pode abortar com um resultado de sua escolha se qualquer solicitação assíncrona falhar.

Sinta-se à vontade para me dispensar se eu tiver entendido mal alguma coisa, mas parece que você está tratando os componentes do React como view e model, quando (parece) eles são apenas a view.

Colora-me desinformado sobre esse assunto, @fdecampredon diz que componentWillMount é assíncrono e você não retorna nada imediatamente, o que o React deve renderizar até que haja, nada? Em caso afirmativo, por que não retornar nada na renderização se ainda não houver dados? (Sim, eu recebo do lado do servidor) Além disso, o que deve acontecer se as props mudarem antes que o componentWillMount seja acionado?

Devo admitir que não pensei em todos os casos ^^.
Esse recurso seria útil apenas na primeira vez que montamos o componente de nível superior e, no servidor, é verdade que, de outra forma, na maioria dos lançamentos, você deseja exibir um indicador de carregador.

Pessoalmente, parece errado despachar solicitações assíncronas durante componentWillMount, a menos que o componente seja realmente uma caixa preta isolada e também implemente um indicador de carregamento. Pelo que entendi, os componentes React não devem ser confundidos com instâncias OOP mais convencionais. Na melhor das hipóteses, um componente React é uma ferramenta para visualizar os dados em props, se for interativo, possivelmente também state. É uma visão, não uma visão e modelo.

De uma forma ou de outra, você desejará que um componente de 'nível superior' seja capaz de recuperar dados, como é feito na amostra Flux .
Neste exemplo as coisas são bem simples porque recuperar a lista de tarefas é uma operação síncrona, se não fosse, e no caso de pré-renderização no servidor, renderíamos uma primeira vez sem dados (e perderíamos o pré-renderizado marcação do servidor).

No caso de uma aplicação simples com um conjunto de dados exibido por uma hierarquia de visualização ainda não há tanto problema, você pode pré-carregar dados e ainda manter a propriedade síncrona de sua loja.
Agora, no caso de um aplicativo composto por vários módulos que você reutiliza em seu aplicativo, gostaria de poder tratar esses módulos como aplicativos separados que podem 'assinar' em diferentes lojas (que seriam responsáveis ​​​​por buscar dados).

Talvez eu entenda as coisas da maneira errada, mas alguma discussão/amostra na web me faz pensar que há algo faltando em algum lugar:

  • Em algumas amostras , parece-me que @petehunt tentou alcançar algo semelhante.
  • react-nested-router promove algum mecanismo semelhante em willTransitionTo , e algumas discussões me fazem sentir que ninguém veio com uma solução adequada.
  • O RRouter também fornece algum tipo de mecanismo para pré-busca de dados enquanto o componente está sendo renderizado/montado.
  • e finalmente react-async como eu disse anteriormente.

@fdecampredon Para ser claro, o propósito de willTransitionTo em react-nested-router é _não_ para carregar dados, especificamente porque retornar uma promessa desse método de fato bloqueará a renderização da nova interface do usuário, o que você não deseja fazer a menos que você absolutamente precise.

@fdecampredon Todo mundo ainda está tentando descobrir as coisas, então não me surpreenderia se ninguém tivesse uma resposta definitiva. Mas acho que os desenvolvedores do Facebook devem ter se deparado com isso algumas vezes.

alguma atualização disso? Acabei de começar a explorar o React e imediatamente me deparei com isso. Muitas pessoas recomendam o React como uma solução para criar aplicativos isomórficos, mas enquanto isso não for resolvido, acho que simplesmente não pode fazer o trabalho.

Para meus ouvidos, isso soa como o problema, os componentes React não deveriam ser os que despacham as solicitações assíncronas, você busca todos os dados e quando esses dados estiverem prontos, só então você chama React.renderComponent. Mesma solução do lado do cliente e do lado do servidor. Você também pode abortar com um resultado de sua escolha se qualquer solicitação assíncrona falhar.

Sinta-se à vontade para me dispensar se eu tiver entendido mal alguma coisa, mas parece que você está tratando os componentes do React como view e model, quando (parece) eles são apenas a view.

Se isso for verdade, o React nada mais é do que uma camada de solução / visualização de modelagem ligeiramente diferente. E isso seria uma pena, porque existe esse potencial. Eu realmente entendo @fdecampredon quando ele menciona esses aplicativos complexos compostos por vários módulos. React seria perfeito para isso.

Eu não acho que essa abordagem significaria tratar um componente como visão e modelo. Se você observar a arquitetura Flux, eles pensam nos componentes React como _controller-views_ que não apenas exibem dados (de lojas), mas também invocam ações baseadas nas interações do usuário. E as ações então atualizam as lojas (= model). Para mim, soa como uma arquitetura MVC óbvia.

O problema está na ação inicial que preenche a loja. É bastante simples no lado do cliente onde a ação pode ser invocada a partir do método componentDidMount() conforme recomendado. No lado do servidor, por outro lado, realmente precisamos de algum lugar especial e algum tipo de mecanismo que atrase a renderização até que a ação seja concluída e as lojas sejam preenchidas.

Neste momento, parece-me que a única maneira é React-async/Fibers + Flux. O legal do Flux é que não precisamos de um cache artificial para transportar os estados dos componentes do servidor para o cliente (como foi feito no exemplo original react-async), podemos simplesmente inicializar as lojas e enviá-las para o client com a marcação html (veja este exemplo ).

Mas esta solução é de fato _hackish_.

Eu não acho que necessariamente precisa ser componentWillMount que é assíncrono; Eu nem tenho certeza de que precisa ser um evento de ciclo de vida. O problema real é que no lado do servidor não há como analisar a árvore de componentes até que tudo tenha sido renderizado em uma string.

Minha solução ideal para resolver isso seria permitir "renderização" que apenas construiria a árvore de componentes, então poderíamos percorrer a árvore para encontrar componentes que precisam de dados adicionais, nos permitir carregar mais dados de forma assíncrona e "renderizar novamente " essa subárvore de componentes e, quando estivermos prontos para liberar a marcação, nos permita converter essa árvore em uma string.

Isso replica o que podemos fazer no navegador: ter um DOM virtual que podemos renderizar novamente como quisermos. A diferença é que no navegador as atualizações do DOM podem ser implícitas. No servidor, precisamos ser explícitos sobre quando renderizamos uma string para que possamos realizar atualizações no DOM virtual com base em dados assíncronos.

Minha solução ideal para resolver isso seria permitir "renderização" que apenas construiria a árvore de componentes, então poderíamos percorrer a árvore para encontrar componentes que precisam de dados adicionais, nos permitir carregar mais dados de forma assíncrona e "renderizar novamente " essa subárvore de componentes e, quando estivermos prontos para liberar a marcação, nos permita converter essa árvore em uma string. — @mridgway

Sim, isso seria bom. Atualmente, renderizar o componente duas vezes no lado do servidor pode ser usado como solução alternativa.

Eu quero fazer referência ao react-nexus como um exemplo do que eu gostaria que fosse suportado no React. É essencialmente uma reescrita de como mountComponent funciona, exceto que ele constrói a árvore de componentes sem realmente montá-la no DOM ou escrever uma string. Isso permite que você percorra a árvore de componentes e dispare métodos assíncronos enquanto a árvore está sendo construída. O problema com esta implementação como ela é, é que ela joga fora aquela primeira árvore e então chama React.renderToString qualquer maneira, enquanto que seria bom pegar aquela árvore de pré-renderização e renderizar/montá-la.

Estou disposto a trabalhar nisso dentro do núcleo, mas precisaria de algumas dicas. Acho que um dos requisitos é fazer com que ReactContext não seja referenciado globalmente para que a sincronização não cause problemas. Existem outros globais que podem ter problemas com isso também?

@mridgway Se não me engano global ReactContext pode ser uma coisa compatível e desaparecerá em 0.14. Mas eu _posso_ estar errado.

@gaearon Sim, esse é o sentido que tive com a depreciação do withContext.

:+1: Usando react-router para este caixa eletrônico. A abordagem do @mridgway parece muito razoável.

Uma abordagem um pouco diferente desse problema é como lidar com o carregamento assíncrono de blocos de código produzidos por um empacotador (como o webpack).

Conforme discutido neste ticket - https://github.com/rackt/react-router/issues/1402 - o problema com o suporte à renderização assíncrona é que a renderização inicial parece ser nula, pois os blocos relevantes ainda não foram carregados no primeiro renderização, resultando em <noscript> na raiz do DOM e falha na soma de verificação. Tudo se encaixa rapidamente depois, mas resulta em uma oscilação na interface do usuário ao trabalhar localmente e pior no campo, especialmente se os pedaços a serem baixados tiverem um tamanho razoável (digamos > 30 KB)

A capacidade de dividir grandes aplicativos em vários pedaços é importante para nós e, tendo resolvido o problema de busca de dados (usamos roteador de reação e rotas aninhadas que podem ser percorridas antes da renderização no servidor para buscar dependências de dados) esta é a última parte do quebra-cabeça que nos impede de avançar totalmente para uma solução React para nosso front-end.

@anatomic Isso não é responsabilidade do React, é seu trabalho separar adequadamente e adiar a renderização até que todos os pedaços necessários tenham sido carregados. Em outras palavras, se um de seus componentes tem uma dependência de alguma biblioteca externa, é obviamente seu problema satisfazer antes de tentar usá-lo, o React não poderia fazer isso mesmo que tentasse, então o mesmo se aplica a todos.

Sinta-se à vontade para implementar estratégias alternativas que possam ser mais adequadas para você, digamos <WaitFor for={MyAsyncLoadedCompSignal} until={...} then={() => <MyAsyncLoadedComp ... />} /> . Mas isso é inerentemente opinativo e não é algo que o React deveria ou precisa oferecer, então é melhor deixar para a comunidade.

É melhor manter as coisas assíncronas fora do escopo dos componentes do React, aqui está um exemplo:

import React from 'react';
import Layout from './components/Layout';
import NotFoundPage from './components/NotFoundPage';
import ErrorPage from './components/ErrorPage';

const routes = {

  '/': () => new Promise(resolve => {
    require(['./components/HomePage'], HomePage => { // Webpack's script loader
      resolve(<Layout><HomePage /></Layout>);
    });
  }),

  '/about': () => new Promise(resolve => {
    require(['./components/AboutPage'], AboutPage => { // Webpack's script loader
      resolve(<Layout><AboutPage /></Layout>);
    });
  })

};

const container = document.getElementById('app');

async function render() {
  try {
    const path = window.location.hash.substr(1) || '/';
    const route = routes[path];
    const component = route ? await route() : <NotFoundPage />;
    React.render(component, container);
  } catch (err) {
    React.render(<ErrorPage error={err} />, container);
  }
}

window.addEventListener('hashchange', () => render());
render();

Veja Webpack Code Splitting , React.js Routing from Scratch e -routing (em breve)

@syranide Vou continuar trabalhando nisso, mas não acho que seja tão binário quanto você colocou acima. Estamos usando o react-router para que isso possa apresentar alguns problemas à mistura, pois o roteador é um componente em vez de ficar fora do ambiente React.

Se fizermos a abordagem <WaitFor ... /> , certamente ainda obteremos um DOM diferente na primeira renderização, o que ainda causará a cintilação/desaparecimento do conteúdo?

Se fizéssemos o abordagem, certamente ainda teremos um DOM diferente na primeira renderização que ainda causará a cintilação/desaparecimento do conteúdo?

Se você não quer flicker (alguns querem) é simplesmente uma questão de esperar que todos os pedaços que você depende sejam carregados antes de renderizar, o webpack fornece isso pronto para uso com require.resolve .

PS. Sim, react-router e qualquer outra coisa que você esteja usando certamente complica a solução, mas ainda não é problema do React resolver.

Se você não quer cintilação (alguns querem) é simplesmente uma questão de esperar que todos os pedaços que você depende sejam carregados antes de renderizar, o webpack fornece isso pronto para uso com require.resolve.

Vou analisar isso, meu entendimento de require.resolve era que era uma pesquisa síncrona do id de um módulo e não envolvia uma viagem ao servidor? Estamos usando require.ensure para gerenciar nosso carregamento de blocos.

Olhando para o nosso código novamente, acho que podemos contornar o problema fazendo com que o react-router pense que está renderizando no servidor, mas seguindo a abordagem padrão do lado do cliente:

const history = new BrowserHistory();

if (typeof history.setup === "function") {
    history.setup();
}

Router.run(routes, history.location, (err, initialState, transition) => {
    React.render(<Provider redux={redux}>{() => <Router key="ta-app" history={history} children={routes} />}</Provider>, document.getElementById('ta-app'));
});

Vou olhar para isso, meu entendimento de require.resolve era que era uma pesquisa síncrona do id de um módulo e não envolvia uma viagem ao servidor? Estamos usando require.ensure para gerenciar nosso carregamento de blocos.

Desculpe, sim, eu quis dizer require.ensure . O callback é executado somente quando todas as dependências estiverem satisfeitas, então é só colocar render/setState dentro dele.

Ok, é mais ou menos assim que estávamos fazendo, obrigado por suas respostas. Isso parece algo que precisa ser abordado no roteador de reação, então continuarei a discussão lá - desculpe se este foi o lugar errado para ter essa conversa!

Você está certo de que o require.ensure é o caminho a percorrer, acho que nosso problema final era que ele deveria estar vinculado à rota atualmente sendo visitada, portanto, está diretamente vinculado ao roteador. Com o react-router sendo baseado em componentes que o vincula à árvore de renderização. Sem meu hack acima, ficamos lutando para ver a árvore antes que tudo seja carregado de forma assíncrona (ou duplicando a lógica de roteamento para permitir que pedaços relevantes sejam carregados no nível superior).

Você está certo de que o require.ensure é o caminho a seguir, acho que nosso problema final era que ele deveria estar vinculado à rota atualmente sendo visitada, portanto, está diretamente vinculado ao roteador. Com o react-router sendo baseado em componentes que o vincula à árvore de renderização. Sem meu hack acima, ficamos lutando para ver a árvore antes que tudo seja carregado de forma assíncrona (ou duplicando a lógica de roteamento para permitir que pedaços relevantes sejam carregados no nível superior).

Não estou intimamente familiarizado com react-router, mas ainda imagino que seja simplesmente um caso de setRoute(...) => require.ensure([], function() { require(...); setRoute(...); }) que realmente não é prático, então você introduz outro nível de indireção. Um mapa de rotas e o carregador assíncrono require.ensure para cada um. Escreva um auxiliar function asyncSetRoute(...) { loadRoute(route, function() { setRoute(...); }} , agora você chama asyncSetRoute e ele adiará a atualização do roteador até que tudo esteja pronto.

Pseudo-código e meio genérico, mas essa parece ser a abordagem geral para mim. Talvez o react-router deva fornecer isso, talvez não... talvez seja idealmente fornecido como um auxiliar externo, não tenho certeza.

Então, depois de horas de pesquisa, só agora estou confirmando que a renderização do lado do servidor é _impossível _ a menos que você alimente tudo de cima para baixo (?).

Possíveis soluções de curto prazo:
A. Pré-preencher uma loja e tornar o carregamento do lado do servidor síncrono

B. Alimente tudo do topo como uma entrada de dados após a busca assíncrona de seu único objeto de dados.

re: 'A'. Isso não funcionará a menos que você já saiba como será sua estrutura de renderização. Eu preciso renderizá-lo para conhecer minhas várias dependências de coleções/modelos/api. Além disso, como faço para que a busca seja assíncrona no cliente, mas sincronize no servidor, sem ter duas APIs diferentes?

re: 'B'. Basicamente o mesmo problema acima. As pessoas estão tendo que fazer pouco JSONs de dependência para acompanhar cada uma de suas rotas?

Existem outras soluções de curto prazo enquanto esperamos por uma solução suportada pelo React? Algum usuário land forks ou plugins? https://www.npmjs.com/package/react-async ?

@NickStefan

Eu não entendo o problema. :-(

  1. Use Flux ou contêiner semelhante ao Flux (Alt, Redux, Flummox, etc) onde as lojas não são singletons.
  2. Defina métodos estáticos como fetchData em seus manipuladores de rota que retornam promessas.
  3. No servidor, combine a rota com os componentes, pegue fetchData deles e espere que eles sejam concluídos antes da renderização. Isso irá pré-preencher sua instância de armazenamento Flux ou Redux. Observe que a instância de armazenamento não é um singleton — ela está vinculada a uma solicitação específica, portanto, as solicitações permanecem isoladas.
  4. Quando as promessas estiverem prontas, renderize de forma síncrona com a instância de armazenamento que você acabou de preencher.

Aqui está um bom tutorial para Redux descrevendo essa abordagem: https://medium.com/@bananaoomarang/handcrafting -an-isomorphic-redux-application-with-love-40ada4468af4

@gaearon Peço desculpas se confundi você. Obrigado pela resposta. Da sua lista, parece que estou correto em supor que a solução para a dependência de dados do servidor é apenas definir as necessidades de dados em seu componente raiz (métodos estáticos / o artigo ao qual você vinculou). Se suas dependências de dados estiverem definidas na raiz, é muito mais fácil pré-preencher os armazenamentos etc.

Esta é uma boa prática de fluxo, mas não é potencialmente limitante? Se eu adicionar um pequeno componente na parte inferior da sua árvore de exibição que precisa de alguns dados muito diferentes, preciso editar as dependências de dados na raiz.

O que estou pedindo é uma maneira de componentes profundamente aninhados definirem necessidades de dados assíncronos.

Se eu tiver que adicionar suas necessidades ao componente raiz, não estou acoplando a raiz às necessidades de um subcomponente?

@NickStefan com -routing , por exemplo, a busca de dados assíncrona se parece com isso:

import Router from 'react-routing/lib/Router';
import http from './core/http';

const router = new Router(on => {
  on('/products', async () => <ProductList />);
  on('/products/:id', async (state) => {
    const data = await http.get(`/api/products/${state.params.id`);
    return data && <Product {...data} />;
  });
});

await router.dispatch('/products/123', component => React.render(component, document.body));

Essas soluções funcionam, mas apenas porque toda a pré-busca de dados está vinculada ao roteador. Isso não é um problema na maioria dos casos (faz sentido, a URL define o que deve estar na página e, portanto, quais dados são necessários), mas em geral é uma limitação. Você nunca será capaz de criar um componente reutilizável autônomo (ou seja, fluxo do Twitter, comentários, calendário) que lidaria com tudo sozinho e tudo o que você precisava fazer era inseri-lo na hierarquia de componentes. A única maneira que encontrei que torna isso possível é reagir assíncrono, mas isso é praticamente um hack.

Suponho que os componentes React simplesmente não se destinam a ser usados ​​dessa maneira, eles ainda são mais visualizações do que _controller_-views. Provavelmente uma biblioteca completamente nova terá que surgir na base do React.

Meu sonho é que um dia possamos escrever um CMS completo usando React, algo como Wordpress, Drupal ou Modx. Mas em vez de snippets HTML/PHP, estaremos compondo o site a partir de componentes React.

@NickStefan

Da sua lista, parece que estou correto em supor que a solução para a dependência de dados do servidor é apenas definir as necessidades de dados em seu componente raiz (métodos estáticos / o artigo ao qual você vinculou). Se suas dependências de dados estiverem definidas na raiz, é muito mais fácil pré-preencher os armazenamentos etc.

Se eu tiver que adicionar suas necessidades ao componente raiz, não estou acoplando a raiz às necessidades de um subcomponente?

Não exatamente na raiz — no nível do manipulador de rotas. Pode haver muitos manipuladores de rota em seu aplicativo. Além disso, bibliotecas como React Router suportam manipuladores de rotas aninhadas . Isso significa que seu manipulador de rotas aninhadas pode definir uma dependência, e a correspondência de roteador conterá uma matriz de manipuladores de rotas hierárquicas correspondentes, para que você possa Promise.all deles. Isso faz sentido?

Não é tão granular quanto “todo componente pode declarar uma dependência”, mas descobri que, na maioria dos casos, limitar a busca de dados a manipuladores de rota (principais e aninhados) é tudo o que eu quero. Eu tenho componentes puros abaixo que aceitam dados via props para que eles nem _saibam_ que estão sendo buscados. Faz sentido que eles não possam solicitá-lo.

A abordagem “todo componente pode declarar uma dependência” não é prática, a menos que você tenha algum tipo de solução de solicitação em lote. Imagine se <User> declara que precisa de uma chamada de API e agora você renderiza uma lista de 100 <User> s — você gostaria de esperar por 100 solicitações? Esse tipo de granularidade de requisito de busca de dados só funciona quando você tem uma solução de solicitação em lote. Se isso combina com você, o Relay é exatamente isso. Mas você precisaria de um backend especial para isso.

@NickStefan No momento, não oferecemos suporte à busca assíncrona por componente. Gostaríamos de adicioná-lo. Esta edição acompanha nosso progresso, embora ninguém esteja trabalhando ativamente nela e será necessária uma grande reestruturação, por isso levará um tempo.

@gaearon

A abordagem “todo componente pode declarar uma dependência” não é prática, a menos que você tenha algum tipo de solução de solicitação em lote.

Depois de pensar um pouco mais sobre isso, eu concordo com você. O que eu estava procurando não é realmente prático.

Eu originalmente pensei que mesmo seus 100exemplo seria bom com disciplina de equipe (ou seja, apenas faça um <UsersContainer> que faça 1 pedido para todos os 100). Mas isso não é "escalável para pessoas" meramente ter uma convenção. Provavelmente é melhor impor todas as dependências à raiz ou ao roteador. Mesmo o Relay meio que força você a declarar dependências na raiz com seus três contêineres diferentes (RelayContainer, RelayRootContainer, RelayRouter etc). Isso meio que prova o ponto de que a única maneira é essencialmente "elevar" suas declarações de dependência na árvore.

Olá!
Existe alguma atualização sobre isso?

+1

Eu mantenho que se você realmente pensa sobre isso, você não quer fazer isso. Eu originalmente pensei que queria que o React fizesse renderização assíncrona. Mas outros neste tópico me convenceram do contrário.

Mesmo no Relay, você basicamente tem que "elevar" suas dependências na árvore com as declarações do contêiner raiz, contêineres do relé etc. A renderização síncrona é o caminho a seguir.

A renderização assíncrona pode ser um pesadelo. Falo por experiência de trabalhar em uma base de código de empresa com backbone que foi hackeada para fazer atualizações individuais em quadros de animação de solicitação.

Acordado. Eu costumava pensar que precisávamos disso, mas na verdade é para trás para que a exibição especifique quais dados carregar. A visualização é uma função do estado do aplicativo, que é uma função da solicitação (e possivelmente do estado do usuário, se houver uma sessão). É disso que trata o React; permitir que os componentes especifiquem quais dados devem ser carregados vai contra a ideia de "fluxo de dados unidirecional", IMO.

na verdade, é para trás para que a exibição especifique quais dados carregar.

Não tenho certeza absoluta de que isso seja verdade. Às vezes, o componente é a única coisa que sabe quais dados carregar. Por exemplo, suponha que você tenha uma visualização em árvore expansível que permita que um usuário navegue em um gráfico enorme de nós - é impossível saber antecipadamente quais dados precisam ser carregados; apenas o componente pode descobrir isso.

Independentemente disso, essa discussão pode se tornar muito mais relevante se perseguirmos a ideia de executar o código React em um web worker (#3092), onde o assíncrono é necessário para se comunicar pela ponte.

No exemplo de uma visualização em árvore grande, se eu realmente quisesse renderizá-la com um caminho já aberto no lado do servidor, adicionaria esse caminho à estrutura da URL. Se houvesse vários componentes complexos como esse, eu representaria seus estados com parâmetros GET. Dessa forma, esses componentes obtêm todos os benefícios do SSR: eles são rastreáveis, podem ser navegados usando o histórico, os usuários podem compartilhar links para um nó dentro deles etc. Agora, também temos uma maneira de o servidor determinar quais dados precisam ser buscado para renderizar uma resposta.

atualização Meu erro é confundir um gráfico com uma árvore, mas ainda sinto que o estado da visão de um usuário desse gráfico deve ser representado pela estrutura de URL. Além disso, não percebi que você realmente trabalha no React. Se você estiver trabalhando em alguma estrutura para integrar uma camada de dados com a visão isomorficamente, isso é incrível. Mas acho que podemos concordar que isso vai além do domínio da visão, e que o React não deve assumir o papel de um controlador de pilha completo.

Não li todo o tópico, desculpe se já foi discutido.

Uma coisa que realmente ajudaria seria se houvesse um método react-dom/server que apenas "iniciasse/instanciasse" um componente e disparasse métodos de ciclo de vida, mas deixasse o desenvolvedor decidir quando ele está pronto para renderizar o componente em uma string html . Por exemplo, o desenvolvedor pode usar setTimeout ou algo mais confiável para aguardar a conclusão dos métodos assíncronos.

Este é o "hack" que estou usando com redux no momento para conseguir isso:

  // start/instantiate component
  // fires componentWillMount methods which fetch async data
  ReactDOM.renderToString(rootEle)

  // all my async methods increment a `wait` counter 
  // and decrement it when they resolve
  const unsubscribe = store.subscribe(() => {
    const state = store.getState()
    // as a result, when there are no more pending promises, the wait counter is 0
    if (state.wait === 0) {
      unsubscribe()
      // all the data is now in our redux store
      // so we can render the element synchronously
      const html = ReactDOM.renderToString(rootEle)
      res.send(html)
    }
  })

O problema com essa abordagem é que o segundo ReactDOM.renderToString dispara todos os métodos componentWillMount novamente, o que resulta em busca de dados desnecessária.

Ei pessoal! isso é algo que está sendo trabalhado atualmente? Acho que essa questão está crescendo em importância. Recentemente, criei uma biblioteca que estende o connect do redux para um dataConnect onde você também pode especificar os requisitos de dados do contêiner. Esses requisitos de dados são então agrupados em tempo de execução e consultados com o GraphQL em uma única solicitação. Eu acho que esse é o futuro das especificações de dados, pois promove a composição e o isolamento e também garante uma busca com mais desempenho. (todos os conceitos vistos no Relay)

O problema com o acima é a renderização do lado do servidor. Como não posso analisar estaticamente com quais requisitos de dados vou acabar, atualmente, precisaria renderizar uma vez para obter o pacote para consultar do banco de dados, hidratar o armazenamento redux e depois renderizar novamente para obter a string final . O problema é semelhante ao de @olalonde .

Seria fantástico poder acionar ações e atualizações na árvore de elementos de reação e obter o resultado da string DOM sob demanda. Aqui está como eu imagino:

const virtualTree = ReactDOM.renderVirtual(rootEle);

// get the bundled query from redux store for example
const bundle = store.getState().bundle;

// Fetch the data according to the bundle
const data = fetchDataSomehow(bundle);

// hydrate store
store.dispatch({type: 'hydrate', data});
// components that should update should be marked on the virtual tree as 'dirty'

virtualTree.update(); // this would only update the components that needed update

const domString = virtualTree.renderToString(); // final result

Outra opção é simplesmente deixá-lo atualizar à vontade como seria no lado do cliente, tendo todos os ganchos presentes e funcionando como didMount. Mas, em vez de alterar o DOM, ele apenas alteraria a representação do dom virtual e também renderizaria para string sob demanda.

O que é que vocês acham? Algo a considerar ou estou vendo completamente errado?

Olá a todos. Estou inscrito nesta edição há mais ou menos um ano. Naquela época, eu achava que precisava ser capaz de especificar os dados que deveriam ser carregados de dentro dos componentes, porque os componentes eram minha unidade primária de modularidade. Esse problema foi muito importante para mim, e pensei que com certeza seria resolvido na próxima versão do React.

Desde então, desenvolvi um respeito muito mais profundo pelos ideais por trás do REST/HATEOS, por causa da simplicidade em grande escala que emerge em um sistema de aplicativos (navegadores, crawlers, servidores de aplicativos, proxies, CDNs, etc.) seguido. No que se refere a esse problema, _a URL deve ser a única representação verdadeira do estado do aplicativo_. Ele, e somente ele, deve determinar quais informações são necessárias para atender a uma solicitação. A camada de visualização, React, não deve determinar isso; deve ser construído com base nos dados. A visualização é uma função dos dados e os dados são uma função da URL .

Eu hesitei em jogar isso lá fora no passado, porque eu continuo ouvindo que esta é uma boa ideia, mas que o mundo real é muito complexo para que isso funcione. E, ocasionalmente, ouço exemplos que me fazem dar um passo para trás e me perguntar se estou sendo muito pedante ou muito idealista. Mas ao refletir sobre esses exemplos, inevitavelmente encontro uma maneira razoável de representar o estado do aplicativo na URL.

  • Seu estado tem parâmetros que variam independentemente? Represente-os como parâmetros de consulta separados.
  • Seu estado (ou parte dele) é grande demais para caber em um URL? Nomeie-o e armazene-o em um armazenamento de dados imutável. Consulte-o pelo nome/ID.
  • Seu estado está mudando tão rápido que você simplesmente não pode armazenar tudo para sempre? Parabéns, você tem big data legítimo, procura armazenamento e começa a pensar em coisas inteligentes para fazer com ele. Ou você pode se esquivar e apenas alterar seus dados com consultas UPDATE e aceitar que não pode armazenar em cache para sempre todas as coisas mais tarde (*).
  • Você tem visualizações diferentes para usuários diferentes, mas veiculadas no mesmo URL, por exemplo, uma página inicial personalizada? Navegue na identificação do usuário para um URL que inclua o ID do usuário.
  • Você está construindo sobre um aplicativo antigo, onde você não tem a opção de quebrar a estrutura de URL antiga? Isso é doloroso, estou no mesmo barco. Redirecione a estrutura de URL antiga para uma estrutura de URL bem arquitetada, traduzindo dados de sessão (ou qualquer outro) para segmentos e parâmetros de caminho de URL.
  • Você tem pouco ou nenhum controle sobre a arquitetura do seu aplicativo? Esse não é o caso para o qual o React foi projetado, e não queremos que o React seja mutilado para se encaixar em algo como uma arquitetura Wordpress / Magnolia / Umbraco mutuamente compatível.

O que você ganha por todo esse trabalho, que você não teria de outra forma?

  • A capacidade de um usuário trazer outro usuário para o mesmo local que ele no aplicativo, compartilhando uma URL.
  • A capacidade de navegar no aplicativo usando todas as ferramentas que o navegador fornece. O cliente padrão está em seu elemento.
  • A capacidade de oferecer paginação complexa de uma maneira simples para o cliente seguir: um link next em uma resposta. A API do gráfico FB é um ótimo exemplo disso.
  • Um gráfico detalhado dos fluxos de trabalho do seu usuário no Google Analytics.
  • A opção de construir o referido gráfico a partir de nada além de logs de solicitação.
  • Uma fuga da rota estrela do caos: em vez de corresponder a todas as solicitações como app.get("*", theOneTrueClientonaserverEntryPoint) , você pode usar sua estrutura de aplicativo de servidor conforme pretendido. Você pode retornar códigos de status HTTP corretos de solicitações, em vez de 200 OK\n\n{status: "error"} . A rota das estrelas é sedutora, mas leva à loucura.

Então, agora que o React, nossa ferramenta estrela, não está controlando a operação, como obtemos nossos dados? Como sabemos quais componentes renderizar?

  1. Encaminhe para uma função de acesso a dados e busque dados para os parâmetros de solicitação relevantes. Repita até ter analisado a URL inteira e ter um objeto de contexto completo.
  2. Transforme o contexto no objeto de resposta mais simples possível, como se você fosse responder a uma solicitação de API. Você está atendendo a uma solicitação de API? Então você está pronto e SECO.
  3. Passe esse objeto minimamente complexo, mas possivelmente grande, para o componente de nível superior. A partir daqui, é a composição do componente React até o fim.
  4. Renderize para string, responda. Deixe seu CDN saber que ele pode armazenar isso em cache para sempre (* a menos que você sacrifique esta opção acima), porque o CDN se identifica por URLs e todos os seus estados têm um URL. Claro, as CDNs não têm armazenamento infinito, mas priorizam.

Não pretendo trollar aqui, mas sinto fortemente que o tema central das solicitações nesta edição está equivocado e que o React não deve implementar nada para acomodá-lo, pelo menos não pelas razões que vi aqui ao longo o ano passado. Alguns aspectos de uma boa arquitetura de aplicativos não são óbvios, e a web é especialmente difícil de acertar. Devemos aprender e respeitar a sabedoria de nossos ancestrais, que construíram a Internet e a Web, em vez de sacrificar a elegância em nome do conforto.

É isso que torna o React ótimo: é a vista. A melhor vista. Somente a vista. λ

Terei que discordar de você @d4goxn. Não estou tentando fazer do React mais do que uma camada de visualização, e o roteamento é importante, mas definitivamente não é suficiente para especificar todos os requisitos de dados. Ao especificar os requisitos de dados em um nível de componente/contêiner, você não está tornando o React mais do que uma camada de visualização. Isso apenas fornece um isolamento e um fluxo de trabalho muito melhores para seu aplicativo. Não é apenas uma simplificação do trabalho, mas uma necessidade de uma grande aplicação. Imagine que meu aplicativo tenha 30 rotas e eu queira adicionar um componente de perfil de usuário em cada uma delas. Seguindo uma rota alternativa onde todos os requisitos de dados são especificados para cada um, você teria que passar pelas 30 rotas para adicionar essa dependência de dados. Quando com se você especificar suas necessidades de dados em um contêiner para esse componente, basta adicionar o componente onde desejar. É plug and play. Não apenas isso, mas a consulta de dependências de dados de todos os componentes pode ser otimizada. O revezamento é um bom exemplo disso, e as conversas sobre isso explicam isso muito melhor do que eu.

Tenho muito respeito pela sabedoria antiga, mas isso não deve ser um limite para evolução e criação de novos padrões, pelo menos é assim que vejo.

Minha proposta é basicamente não alterar o React, mas ter uma forma 'somente virtual' de alterar a árvore dom/components, basicamente um React que pode ser 'executado' no lado do servidor, o que acho bem simples de fazer (basta bloquear ações para alterar o DOM). Você ainda pode ter cache e ter um CDN no lugar. Com a crescente dinâmica de sites e aplicativos hoje em dia o cache estático tende a diminuir mas isso é outro assunto 😄

Se a exibição especificar dependências de dados, você precisará de alguma maneira de otimizar o gráfico de dependência e transformá-lo em um número mínimo de consultas; você não pode preparar os dados antes de construir a exibição, embora possa dividi-la em duas fases, que é o que entendo ser o plano neste segmento. Pegue uma coleção de componentes moderadamente complexos, cada um com uma consulta própria, por exemplo; talvez nem todas sejam instâncias do mesmo componente e não haja um padrão que possa ser recolhido em uma única consulta. Suponho que é isso que o GraphQL está abordando. Você ainda precisará implementar ou integrar servidores GQL para cada um de seus datastores. Classificar quais partes da consulta GQL otimizada devem ser tratadas por qual armazenamento de dados parece bastante complexo, mas minha proposta também tem um pouco dessa complexidade.

No exemplo, onde há um grande número de rotas que precisam dos mesmos dados, na verdade eu não veria isso como um motivo para excluir o identificador dessa fonte de dados da URL. Eu montaria um pequeno módulo de middleware de busca de dados bem próximo à raiz da pilha, que anexaria esse objeto de usuário a um contexto e depois passaria o contexto para mais middleware, a caminho de um manipulador de rota final. O componente raiz React pode não se importar com essa parte específica do contexto, mas passaria para o próximo nível de filhos que se importariam com isso, ou teriam descendentes próprios que se importariam. Se isso introduzir uma fiação excessivamente complexa ou profunda, algo como uma loja Flux pode ser necessária, mas esse é um grande tópico por si só.

Separação e isolamento: sinto sua dor ali. Na minha proposta, temos dois sistemas muito diferentes, transformando dados de um formulário otimizado para armazenamento e recuperação em um formulário otimizado para simplicidade abstrata. Então, minha visão é transformar isso em uma forma que se adapte a ele, pois é passado pela hierarquia de componentes. Manter esses dois sistemas frouxamente acoplados ao adicionar um recurso que requer adições a ambos os sistemas não é o que eu chamaria de difícil, mas também não é o caminho de menor resistência. A mudança mental entre a programação de um armazenamento de dados e a programação de uma interface do usuário dinâmica é real. Costumávamos ter desenvolvedores de back-end e front-end separados, e o HTTP servia como interface entre esses dois paradigmas. Agora estou tentando internalizá-lo em um aplicativo e você está tentando delegá-lo.

Não estou convencido de que a crescente complexidade e atividade nos aplicativos impeça que o estado do cliente seja representado por um objeto muito pequeno, que faz referência a um conjunto de dados muito maior no servidor. Considere um jogo de tiro em primeira pessoa multiplayer massivo: há muita coisa acontecendo rapidamente e você deseja transmitir a quantidade mínima de informações necessárias do cliente para o host/servidor do jogo. Quão pequeno você poderia conseguir isso? Um mapa de todos os estados de entrada; um carimbo de data/hora + intervalo de incerteza, um campo de ~ 110 bits para o teclado e algumas dezenas de bytes para a orientação do mouse / joystick e do headset VR. O cliente estaria prevendo, renderizando e fisicamente com otimismo, e o servidor estaria derivando uma enorme quantidade de informações do pequeno estado e histórico de estado do cliente e retornando uma quantidade bastante grande de dados para corrigir e atualizar todos os clientes ( todos os mesmos parâmetros para todos os agentes próximos, push de ativos), mas esse fluxo de solicitações de clientes ainda pode se encaixar confortavelmente em solicitações de 2 KB. E isso pode ser um benefício para a arquitetura do aplicativo.

Não vou pedir para você me ensinar sobre Relay e GraphQL; Eu os explorei apenas superficialmente, antes que suas APIs atingissem uma versão estável (elas estão bloqueadas agora?) é um bom plano, então talvez seja hora de dar outra olhada neles. Eu tenho algumas perguntas difíceis sobre essa arquitetura, mas estou prestes a sair do assunto.

:cervejas:

PS Eu não quis sugerir que HTTP seria um bom protocolo de comunicação para um MMOFPS

@d4goxn Eu entendo completamente sua hesitação e ceticismo sobre isso. Nunca pensei nisso até começar a trabalhar com GraphQL e depois a introdução dos conceitos do Relay. Eu realmente recomendo que você dê uma olhada. E implementar o GraphQL em um servidor novo ou existente não é tão difícil quanto você imagina, mesmo se você tiver vários armazenamentos de dados. Não querendo sair do tópico também, mas esta é uma discussão importante.

Acredito que esse nível de abstração seja o caminho a percorrer. Eu tenho usado isso no Relax CMS e o fluxo de trabalho e o isolamento são uma felicidade para trabalhar. Não só isso, mas os dados solicitados não são mais, nem menos, o que a interface do usuário precisa, e isso é feito automaticamente coletando quais dados cada componente precisa e mesclando-os. Isso é feito pelo Relate http://relax.github.io/relate/how-it-works.html se você estiver interessado em verificar. Com isso também posso incluir qualquer componente em qualquer lugar da minha aplicação e ter certeza de que funcionará como um bloco independente da minha aplicação.

A única coisa ainda a descobrir é a renderização do lado do servidor. Depois de passar pelo código-fonte do react, acredito que já possa haver uma solução como descrevi, caso contrário, poderia trabalhar em uma solução se alguém da equipe do react concordar com isso.

@d4goxn também me atrevo a discutir com você :) Sua postagem original contém uma declaração de que a visualização é uma função dos dados e os dados são uma função da URL . Eu não chamaria isso exatamente revelador. Aplica-se praticamente a qualquer site ou aplicativo da Web que não seja completamente aleatório (há uma dúvida se, por exemplo, o estado das janelas de diálogo abertas também deve fazer parte da URL). Como todos nós tivemos matemática na escola, sabemos que essa função pode ser composta. Portanto, a afirmação real pode ser que o que você vê na tela é uma função da URL . Novamente, nada realmente revelador, é apenas uma formalização do óbvio.

Para mim, a principal questão é como construir tal função .

A abordagem que você sugere se parece muito com os bons e velhos aplicativos MVC do lado do servidor (por exemplo, Spring MVC é um bom exemplo). A URL atual ativa o método do controlador correspondente que _faz a lógica de negócios_. Ele busca todos os dados necessários e os passa para a exibição. Meu problema com isso é o seguinte: Quando você olha para a página da web gerada, é algum tipo de hierarquia de componentes (não necessariamente componentes React). O que você precisa fazer é criar ou gerar a hierarquia a partir da URL. Mas você tem que fazer isso duas vezes! Primeiro, você precisa decodificar a hierarquia no controlador para saber quais dados você precisa buscar e, segundo, você precisa decodificar a hierarquia da visão para... bem... renderizar a visão real. Eu não acho que seja uma abordagem muito _DRY_.

Eu não estou tão familiarizado com o Spring MVC, mas eu uso muitas vezes outro framework MVC (framework PHP chamado Nette) que aborda esse problema de uma maneira muito semelhante a como eu gostaria de usar o React. Esta estrutura também suporta componentes. A idéia é que para cada componente (por exemplo, um formulário) você defina uma fábrica em seu código que seja responsável por instanciar o componente e principalmente por _carregar todos os dados necessários_. Tal componente é então possível de ser incluído em qualquer lugar na visão. Assim, conforme você escreve o código HTML e cria a _view hierarquia_, você pode simplesmente inserir um componente e a fábrica subjacente cuida da inicialização necessária. O componente se comporta como um pequeno controlador independente, tem seu próprio ciclo de vida, trata de suas dependências e pode até ser parametrizado a partir da visão o que aumenta sua reutilização.

Eu tenho usado essa abordagem com React no lado do cliente também e parece muito viável. Os componentes React foram desde o início indicados como _controller-views_ e é assim que eu os uso. Eu tenho meus componentes _controller_ que são responsáveis ​​pela busca de dados e então eu tenho meus componentes _view_ que se preocupam apenas com os visuais. A página inteira é composta de componentes de ambos os tipos. Eles são bem desacoplados e facilmente reutilizáveis.

Minha aplicação não é isomórfica (ou universal como eles chamam hoje) porque eu não precisava dela, mas a pilha que estou usando deve ser capaz disso (e acredito que, além do Relay, que ainda é experimental, essa pilha caminhou caminho mais longo nesta matéria). FYI, é assim que se parece:

  • A ferramenta principal é o react-router . Ele permite ligar a solicitação de dados assíncrona em cada URL e funciona tanto no lado do cliente quanto no lado do servidor. Formalmente falando, essa abordagem sofre do problema que mencionei anteriormente. Esses componentes _controller_ não são possíveis. Mas o que importa aqui é o design de react-router . Permite definir tanto a _hierarquia de dados_ quanto a _visão/hierarquia de componentes_ no mesmo local e as definições de rota reais também são hierárquicas. Parece muito _React-like_.
  • Todo o estado (os dados) é tratado com redux . Além de todas as outras vantagens, ele mantém todo o estado em um único objeto, o que significa que é muito simples e natural criar o objeto no servidor e enviá-lo ao cliente. Também graças à abordagem connect() , não é necessário passar todos os dados do nível superior para baixo usando props (isso seria absolutamente louco, considerando o tamanho atual do meu aplicativo).
  • Outra peça do quebra-cabeça é a reseleção, que nos permite manter o estado o menor possível. E também aumenta muito o fator de desacoplamento.

Ainda não é perfeito, mas é um progresso significativo em relação ao que estava disponível quando me deparei com este tópico. A implementação de amostra pode ser encontrada aqui .

Eu também tenho quase todo o trabalho isomórfico, exceto que preciso de uma maneira de realizar a renderização do lado do servidor quando as buscas de dados estão no nível do componente. Sem adicionar ao argumento filosófico agora, por um longo tempo eu tenho usado com sucesso o vanilla react (mais roteamento caseiro) com componentes buscando seus próprios dados em componentDidMount. Eu realmente não gosto da ideia de manter uma estrutura redundante para definir meu componente e dependências de dados - isso já está definido na minha árvore de componentes, e aqui eu de alguma forma preciso talvez de várias passagens de renderização que incorporem dados à medida que são buscados de forma assíncrona, até o componente completo árvore foi resolvida.

No momento, eu só queria destacar a crescente importância disso para a reação isomófica, imo. Vou tentar encontrar uma solução enquanto isso. Obrigado.

@decodeman caso você esteja interessado, para Relate, eu fiz uma travessia de reação mínima para obter dependências de dados dos componentes https://github.com/relax/relate/blob/master/lib/server/get-data -dependencies.js. Dessa forma, posso obter todas as dependências de dados -> buscar os dados -> reagir renderizar à string.

@decodeman , FWIW, usamos a abordagem de renderização dupla (como o que você alude) com sucesso em um aplicativo de grande produção. Ele é inspirado por este exemplo redux-saga , mas a abordagem geral deve funcionar bem com qualquer tipo de carregamento assíncrono. Você só precisa mover o carregamento para componentWillMount . Para ajudar a mantê-lo gerenciável, também temos componentes de ordem superior que cuidam de cenários comuns de carregamento (por exemplo, verifique prop x e carregue se nil.

Eu também acho que seria muito importante poder carregar dados dentro dos componentes e ter algum tipo de método de renderização preguiçoso. Talvez para deixar os dois lados felizes crie um novo método chamado asyncRenderToString e adicione um novo evento de "ciclo de vida" chamado asyncOnLoad ou algo assim. Isso só será chamado se você chamar um método asyncRender. Em termos de duplicação de código entre o lado do servidor e do cliente, isso pode ser corrigido pelos desenvolvedores apenas movendo o código de carregamento comum para um método separado e chamando esse método de asyncOnLoad e componentMount . Detectar se o asyncOnLoad foi feito provavelmente deve usar um Promise retornado (se undefined for retornado/método não definido, ele deve chamar diretamente os eventos de ciclo de vida aplicáveis ​​e depois renderizar), caso contrário, ele espera até que a promessa seja resolvida.

Acho que uma solução como essa resolveria todas as maneiras hacky que temos que usar no momento para renderização adequada do lado do servidor. Incluindo, por exemplo, permitir a renderização fácil do lado do servidor de aplicativos react com o roteador react v4 (que não usa mais uma configuração de rota centralizada que é agora a única maneira de fazer aplicativos isomórficos funcionarem)

Isso foi resolvido seu chamado stream check out react stream.

Na quinta-feira, 3 de novembro de 2016, Florian Krauthan [email protected]
escreveu:

Eu também acho que seria muito importante poder carregar dados dentro
componentes e tem algum tipo de método de renderização preguiçoso. Talvez para fazer os dois
lados felizes crie um novo método chamado asyncRenderToString e adicione um novo
evento de "ciclo de vida" chamado asyncOnLoad ou algo assim. Isso vai
só será chamado se você chamar um método asyncRender. Em termos de código
duplicação entre o lado do servidor e do cliente isso pode ser corrigido pelos desenvolvedores
de apenas mover o código de carregamento comum para um método separado e chamá-lo
método de asyncOnLoad e componentMount. Detectando se asyncOnLoad é
done provavelmente deve usar uma promessa retornada (se undefined for
retornado/método não definido deve chamar diretamente o método aplicável
eventos do ciclo de vida e depois renderiza), caso contrário, ele espera até a promessa
resolve.

Acho que uma solução como essa resolveria todas as maneiras hacky que temos que usar
no momento para renderização adequada do lado do servidor. Incluindo, por exemplo, permitir
renderização fácil do lado do servidor de aplicativos react com o roteador react v4
(que não usa mais uma configuração de rota centralizada que agora é a
única maneira de obter aplicativos isomórficos funcionando)


Você está recebendo isso porque está inscrito neste tópico.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/facebook/react/issues/1739#issuecomment -258198533,
ou silenciar o thread
https://github.com/notifications/unsubscribe-auth/ATnWLUIEJw4m1Y3A4oGDOBzP6_ajDcqIks5q6g4_gaJpZM4CHAWq
.

@yamat124 se eu procurar por https://github.com/aickin/react-dom-stream

Pelo menos com base na documentação, não há como ter um evento de ciclo de vida para pré-carregar dados que atrasa a próxima chamada de renderização para subfilhos até que a promessa seja resolvida. Ou você está falando de outra coisa?

Mais uma vez, essas são todas as maneiras muito hacky. Requer que o roteamento seja independente da árvore de componentes. E o componente "Principal" que a rota inclui tem que conhecer todos os dados que precisam ser carregados (Funciona 99% do tempo, mas nem sempre). Ambos seriam obsoletos se o react tivesse uma renderização de componente atrasada.

Concordo, esta é provavelmente a coisa que eu mais odeio agora sobre reagir. Odeio quando as pessoas tentam defender o framework de não ter esse recurso dizendo que pode ser feito manualmente, claro que pode, como tudo relacionado ao desenvolvimento de software, mas isso não quer dizer que deva. O fato de que existem toneladas de módulos por aí cuidando se essa coisa precisa faz um ponto por si só que é algo que deve ser resolvido de dentro da própria estrutura.

Next.js tem um async getInitialProps para fazer renderização do lado do servidor, infelizmente ainda precisa chamar o getInitialProps do filho dos pais até a raiz. O cliente Apollo GraphQL também permite percorrer toda a árvore para reunir os requisitos de dados do lado do servidor e enviar tudo de volta. Exemplo de Next + Apollo: https://github.com/sedubois/realate.

Seria ótimo ter métodos assíncronos de ciclo de vida do componente React para combinar essas coisas.

Acabei de aprender sobre SSR esta semana, espero que o que escrevo faça sentido 😄

/cc @nkzawa @stubailo

BTW componentWill/DidMount já são assíncronos, isso ajuda?

https://twitter.com/Vjeux/status/772202716479139840

@sedubois o React espera que a promessa seja resolvida antes de renderizar? Caso contrário, não ajuda muito!

@olalonde parece que sim: https://github.com/sedubois/react-async-poc/blob/1d41b6f77e789c4e0e9623ba1c54f5ed8d6b9912/src/App.js

const getMessage = async () => new Promise((resolve) => {
  setTimeout(() => {
    resolve('Got async message!');
  }, 3000);
});
class App extends Component {
  state = {
    message: 'Loading...',
  };
  async componentWillMount() {
    this.setState({ message: await getMessage() });
  }
  render() {
    return (... {this.state.message} ...);
  }
}
loadinggot

@sedubois Observe que, no seu exemplo, componentWillMount

  async componentWillMount() {
    this.setState({ message: await getMessage() });
  }

retorna undefined e uma promessa, mas as capturas de tela mostram que o React não espera (não poderia) pela promessa, porque renderiza o texto Loading... .

Editado: corrigido.

@sedubois @polytypic Também fará com que todos os erros lançados dentro da função sejam ignorados silenciosamente, já que a Promise não é tratada.

@polytypic @dantman Não tenho certeza do que significa "a promessa não é tratada". É aguardado pelo operador await . Você precisa tentar capturar o erro, atualizei o exemplo (usando assinatura de carregamento/erro semelhante ao que o Apollo faz).

    try {
      this.setState({ message: await getMessage() });
    } catch(error) {
      this.setState({ error });
    }

E renderize:

{error ? error : loading ? 'Loading...' : message}
screen shot 2016-11-07 at 13 25 46

@sedubois Quando você async uma função, ela retorna implicitamente uma promessa, qualquer throw na função se transforma implicitamente em uma rejeição dessa promessa em vez de jogar a pilha. Isso significa que componentWillMount agora está retornando uma promessa para seu chamador.

O React não faz nada com o valor de retorno, então sua função agora está retornando um Promise esperando que alguém esteja fazendo algo com ele, mas está apenas sendo descartado.

Por não ser tratado, isso significa que qualquer rejeição dessa promessa não será capturada por nenhum código no React e qualquer erro não tratado não aparecerá no seu console.

E mesmo fazendo um try..catch não irá protegê-lo perfeitamente disso. Se setState lançar um erro de sua captura ou você cometer um erro de digitação na captura, esse erro desaparecerá silenciosamente e você não perceberá que algo está quebrado.

@sedubois Na verdade, um método assíncrono sempre parece retornar uma promessa em JavaScript (bem, JavaScript futuro). Fiquei confuso pensando em problemas com async-await em C #. _Desculpe pelo barulho!_

No entanto, as capturas de tela mostram claramente que o React não espera que a promessa seja resolvida. Ele chama render imediatamente após chamar componentWillMount e renderiza o texto Loading... . Então ele chama render novamente após a chamada para setState . Se o React esperasse que a promessa fosse resolvida, ele não renderizaria o texto Loading... . Em vez disso, ele esperaria até que a promessa fosse resolvida e só então chamaria render , que é o tipo de comportamento desse problema: ser capaz de executar E/S assíncronas durante a renderização do lado do servidor.

@dantman @polytypic obrigado 👍 Sim, com certeza são necessárias alterações dentro do React para aguardar a resposta e lidar com os erros corretamente. Talvez adicionar alguns await aqui e ali em seu código possa fazer isso 😄

Pessoalmente, estou usando Web Workers para armazenar o estado do aplicativo e emitir props na alteração (já que desvinculei completamente o armazenamento de dados e a busca de componentes do React e trato o React apenas como renderizador, para preservar o princípio do fluxo unidirecional), então ele apenas espera até que um mensagem com os requisitos esperados (como uma propriedade específica sendo verdadeira) para renderizar.

Dessa forma, mesmo que a(s) primeira(s) mensagem(ns) sejam estados intermediários de "carregamento", elas não são usadas para renderizações estáticas do lado do servidor, sem a necessidade de métodos de componentes assíncronos.

@sedubois Teria que ser uma pilha diferente de métodos; ou uma mudança de ruptura. Os métodos de renderização atuais do React são sincronizados, então precisaríamos de um método de renderização de servidor diferente que retornasse uma promessa.

@dantman Eu não quero dar nenhuma opinião aqui, pois essas águas são muito profundas para mim (meu objetivo inicial era apenas apontar o componente assíncrono do Vjeux, WillMount hack ...), mas hmm, não posso React olhar para o tipo de objeto retornado pelos métodos do ciclo de vida e se for uma promessa, tratá-lo de forma assíncrona e, caso contrário, tratá-lo de forma sincronizada (como atualmente, ou seja, de maneira compatível com versões anteriores)?

Eu gosto do que @fkrauthan propôs. Um método de ciclo de vida load que pode retornar uma promessa ou undefined e uma função adicional assíncrona renderToStringAsync . Mas load deve sempre ser chamado, no cliente e no servidor. Se usarmos render ou renderToString a promessa retornada será ignorada para corresponder ao comportamento que temos hoje. Se usarmos renderToStringAsync e load retornar uma promessa, a promessa deverá ser resolvida antes da renderização.

Por que não fazer como Next.js , adicionar uma função de ciclo de vida React async getInitialProps que é chamada antes do construtor? Se não houver tal método, chame o construtor diretamente.

const props = await (Component.getInitialProps ? Component.getInitialProps(ctx) : {});
...
const app = createElement(App, {
  Component,
  props,
  ...
});

Parece que o Next.js consegue fazer o trabalho, exceto que seu getInitialProps não está conectado ao ciclo de vida do componente React, então ele só pode ser chamado no componente de nível superior. Então, as ideias estão lá e poderiam simplesmente ser unidas?

@sedubois Eu não acho que getInitialProps seja o lugar correto. Em primeiro lugar, as pessoas que estão usando o ES6 não têm/usam esse método. Em segundo lugar, você não quer trabalhar no initalProps (que são apenas os padrões), você quer trabalhar em cima da mesclagem de initalProps + passado em props. Portanto, minha sugestão de um novo evento de ciclo de vida que seja despachado antes de componentWillMount .

Acho que a ideia do @Lenne231 pode causar alguns problemas, pois as pessoas podem transmitir outros eventos do ciclo de vida para que essa promessa já esteja resolvida. Ter agora dois comportamentos diferentes do mesmo evento de ciclo de vida torna isso um pouco complicado.

Portanto, deve haver no servidor e no cliente um método de renderização de sincronização que não chame esse novo ciclo de vida. E uma versão assíncrona que chama esse ciclo de vida e sempre espera até que a promessa seja resolvida antes de prosseguir. Isso, na minha opinião, daria a você a maior flexibilidade, pois agora você pode decidir se precisa desse ciclo de vida na renderização do cliente também ou apenas na renderização do servidor.

pessoas que estão usando ES6 elas não têm/usam esse método

@fkrauthan talvez você se refira a getInitialState ? Aqui eu estava falando de um novo, getInitialProps (o nome usado em Next.js).

Quanto ao seu segundo ponto, sim, pode ser melhor ter o novo método de ciclo de vida antes de componentWillMount vez de antes de constructor . Eu acho que não é assim no Next porque eles não têm o luxo de ajustar o ciclo de vida do React como mencionado anteriormente. Por isso, seria ótimo trazer essas ideias para o React.

@sedubois Até aquele. Eu uso por exemplo o recurso es7 e defino static props = {}; no meu corpo de classe. Isso torna o código muito mais agradável de ler na minha opinião e tenho certeza que mais e mais pessoas vão mudar para isso assim que o es7 for lançado oficialmente.

Nota: as propriedades de classe não são um recurso "ES7" porque o ES7 já foi finalizado e o único novo recurso de linguagem que existe é o operador de exponenciação. O que você está se referindo é a proposta de propriedades de classe do estágio 2 . Ainda não faz parte do idioma e pode mudar ou ser descartado.

Não vejo a necessidade de funções assíncronas em reagir para buscar dados, em vez disso, ficaria feliz com um renderizador virtual que renderizariaToString sob demanda. Assim, você pode definir ações e a árvore virtual será atualizada de acordo antes de renderizar para string. Isso eu acho que seria ideal para conseguir um bom SSR em reagir. Deixa para o desenvolvedor como e onde buscar os dados.

Eu mencionei antes, mas uma boa API seria algo como o seguinte (com uma pequena atualização com o que sugeri antes):

const virtualTree = ReactDOM.renderVirtual(rootEle);

// get the bundled query from redux store for example
const bundle = store.getState().bundle;

// Fetch the data according to the bundle
const data = await fetchDataSomehow(bundle);

// hydrate store (this will set updates on the virtual tree)
store.dispatch({type: 'hydrate', data});

// final result
const domString = virtualTree.renderToString();

Isso evitaria o problema que alguns clientes GraphQL, como o Apollo, estão tendo, que os obriga a fazer um renderToString duplo. Sendo o primeiro a buscar dependências de dados, e o segundo renderiza com os dados preenchidos. Eu acho que isso é um desperdício de processamento, já que renderToString é bastante caro.

@bruno12mota, entendo seu ponto, mas, novamente, apenas a quantidade de bibliotecas que tentam simular isso (e com sua solução ainda haverá milhares de bibliotecas) é para mim um sinal de que talvez esse deva ser um recurso principal, em vez de algo que você precisa construir em cima. Sua solução ainda exigiria muito mais chamadas de renderização (virtuais, mas ainda chamadas de renderização) para obter a saída.

@fkrauthan sim, mas as renderizações virtuais têm muito mais desempenho do que ter que fazer duas renderizações para string, que é o principal problema aqui na minha opinião (desempenho no SSR) é a parte fraca do React. Houve alguns experimentos para melhorar o renderToString react, mas não há nenhum avanço real sobre esse assunto pela equipe do react (não criticando aqui, eles fazem um trabalho incrível).

Concordo com @bruno12mota , já tendo deliberado sobre esse assunto há algum tempo, essa é a abordagem mais simples. Ele deixa a liberdade para o desenvolvedor determinar quando sua renderização deve "fluir".

Isso torna tudo muito mais complicado.

1.) Do ponto de vista do código (eu examinei o código e tornar a renderização assíncrona deve ser muito mais fácil do que criar um renderizador que usa apenas VDom e depois pode ser despejado em um ponto)
2.) Agora você tem que reconsiderar a desmontagem de eventos também. E torna o código personalizado muito mais complicado do que o necessário. Por exemplo, carregar dados pode resultar na renderização de um novo componente que também precisa carregar dados. Agora, de repente, a lib precisa descobrir qual componente em qual posição já carregou os dados e qual componente contém novos carregamentos de dados e coisas.

Estou com @bruno12mota. Um VDom é sem dúvida uma solução mais "de baixo nível", mas provavelmente seria mais fácil para os desenvolvedores do React implementarem, não teria problemas de compatibilidade com versões anteriores e poderia permitir que a comunidade experimentasse diferentes soluções de nível superior.

Por exemplo, uma solução de nível superior pode ser um "componente de nível superior" que envolve métodos componentWillMount assíncronos, aguarda todas as promessas pendentes e dispara VDom.renderToString() quando tudo é resolvido. Estou usando esse tipo de estratégia para SSR em meu aplicativo, embora, infelizmente, faça muitas buscas agora porque não há VDom.

Acho que a melhor maneira seria não alterar nenhuma função que temos atualmente. O que significa não fazer com que getInitialState, componentWillMount ou renderToString se comportem de forma diferente.

Em vez disso, devemos adicionar novas funções para resolver o problema discutido aqui. Assim como existem funções que são chamadas apenas no cliente, pode haver funções que são chamadas apenas no servidor.

Acho que poderíamos adicionar uma função "renderToStringAsync" para react-dom/server. A diferença para o renderToString normal seria que ele retorna uma Promise.

No lado da implementação, a única diferença seria que a versão assíncrona, sempre que criasse uma instância de Component, chamaria e aguardaria "initialize()" primeiro, antes de chamar render(). Se o componente não tiver um método "initialize", ele poderá chamar render() imediatamente.

Então, se tivermos o seguinte exemplo de estrutura de aplicativo:

    ComponentB
    ComponentC
        ComponentD
        ComponentE

o processo seria:

1) instanciate componentA
2) await componentA.initialize();
3) componentA.render()
4) do in parallel(
    instanciate componentB, await componentB.initialize(), componentB.render()
    instanciate componentC, await componentC.initialize(), componentC.render(), do in parallel(
        instanciate componentD, await componentD.initialize(), componentD.render()
        instanciate componentE, await componentE.initialize(), componentE.render()
    )
)
5) render to string

Então, basicamente, precisamos apenas de uma nova função "renderToStringAsync" que faz uso de uma nova função opcional "initialize". Não seria tão difícil.

@VanCoding tem a mesma ideia que venho pensando há algum tempo. Eu queria saber se inicializar também poderia ser chamado automaticamente em componentDidMount no cliente?

Então no servidor seria:

initialize()
render()

E no cliente seria:

render() // without data (unless available synchronously)
componentDidMount()
initialize()
render() // with data

Eu tenho uma implementação de trabalho de renderização de servidor react com react-router v4 e react-apollo com GraphQL.

foi inspirado em server-side-rendering-code-splitting-and-hot-reloading-with-react-router .

Basicamente, a renderização do servidor usa o componente sincronizado, a renderização do cliente usa o componente assíncrono para que o carregamento assíncrono do webpack e a execução do módulo javascript.

A propósito, a renderização do servidor é implementada com o Nashorn Script Engine (JVM).

Código de renderização do cliente
https://github.com/shendepu/react-ssr-starter-kit/blob/apollo/src/main.js#L63 -L82

Código de renderização do servidor
https://github.com/shendepu/react-ssr-starter-kit/blob/apollo/src/main.js#L128 -L155

Há um lugar importante para distinguir o componente sincronizado ou assincronizado na rota.
Comonente sincronizado na rota
https://github.com/shendepu/react-ssr-starter-kit/blob/apollo/src/routes/Counter/Route.js#L10

Componente assincronizado em RouteAsync
https://github.com/shendepu/react-ssr-starter-kit/blob/apollo/src/routes/Counter/RouteAsync.js#L7 -L23

OBSERVADO: o nome do arquivo DEVE ser Route.js ou RouteAsync.js. O código deve sempre importar RouteAsync.js. O Webpack irá substituí-lo por Route.js se for construído para renderização de servidor.
https://github.com/shendepu/react-ssr-starter-kit/blob/apollo/build/webpack.config.js#L72 -L80

Portanto, haverá duas versões de compilação dist e dist_ssr , dist é para cliente enquanto dist_ssr é para servidor que tem apenas dois pedaços: fornecedor e aplicativo . O estado da loja é exportado e incorporado em <script> de index.html como __INITIAL_STATE__ . A razão para ter duas versões de compilação é que ReactDomServer.renderToString() ainda não suporta o componente assíncrono.

O único problema com a abordagem de compilação de duas versões é que há um aviso no modo dev:
React attempted to reuse markup in a container but the checksum was invalid. This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server:

Mas como a única diferença entre a versão do cliente e do servidor é Route.js e RouteAsync.js, o aviso pode ser ignorado, o aviso não é exibido no modo de produção.

Para busca de dados assíncrona antes da renderização do componente no lado do servidor:

  • Rest API: Adicionado loadData estático no uso do componente
const { matchedRoutes, params } = matchRoutesToLocation(rootRoute.routes, 
                                                        location, [], {}, rootRoute.pattern)
matchedRoutes.filter(route => route.component.loadData).map(route =>
              route.component.loadData(store, params))

para sondar e executar loadData . matchedRoutes é uma matriz de rotas correspondentes cujo pai é do índice 0 . loadData pode ser executado em sequência ou em paralelo.

  • Consulta GraphQL: basta usar getDataFromTree de react-apollo/server para executar todas as consultas GraphQL.

BTW: usar o Nashorn Script Engine tem uma oportunidade de otimização de desempenho: como o fornecedor contém código de biblioteca e polyfills que são executados uma vez e reutilizados, para solicitações posteriores, apenas o trecho do aplicativo é executado. Como as bibliotecas são movidas para o bloco do fornecedor, o aplicativo contém apenas código javascript no src, que é pequeno, portanto, é um aumento de desempenho em comparação com a avaliação do bloco do fornecedor todas as vezes. (todo javascript é compilado uma vez e armazenado em cache, executado para cada solicitação.)

Eu menti que o pedaço do aplicativo contém apenas código src. ele também contém babel-runtime e core-js/library duplicado referenciado por babel-runtime . babel-runtime não pode ser movido para o vendor chunk porque ele não tem index.js ou entrada principal, então o webpack não pode reconhecê-lo como um módulo. mas o tamanho do código é pequeno.

Exemplo Vamos aprofundar @VanCoding 's: https://github.com/facebook/react/issues/1739#issuecomment -261577586

Talvez inicializar não seja um bom nome - o nome deve deixar claro que se destina a ser usado apenas no servidor ou em um ambiente em que a renderização é atrasada até que essa função retorne - ou seja, não no cliente. Ideias: getStaticProps , getServerProps , getInitialProps , getAsyncProps ...

Também seria bom ouvir da equipe principal se há impedimentos técnicos ou de outra forma para desenvolver esse método renderToStringAsync.

@nmaro Bem, acho que a função não deve retornar nada, então prefiro um nome que não comece com "get", mas fora isso, ainda acho que esse seria o melhor caminho. Eu não me importo com a forma como é chamado no final.

Além disso, fiz uma prova de conceito para isso , em cerca de 40 linhas de código. Não se destina ao uso em produção, mas meus testes com vários componentes foram bem-sucedidos.

Portanto, se a equipe do react ainda não quiser que isso aconteça, não seria tão difícil fazê-lo em uma biblioteca de terceiros.

Alternativamente, poderíamos adicionar uma opção para renderToString, como em
renderToString(Component, {async: true})

@nmaro Isso tornaria o tipo de retorno da função dependente das opções fornecidas, o que não considero uma boa prática. Eu acho que uma função deve sempre retornar um resultado do mesmo tipo.

@VanCoding você tem um exemplo de como usar seu código como uma biblioteca de terceiros?

@VanCoding legal! Mas honestamente, acho que vai ser difícil integrar isso no algoritmo de renderização interno do react. Confira https://github.com/facebook/react/blob/master/src/renderers/dom/stack/server/ReactServerRendering.js e se perca nos internos do react... IMO, precisamos muito de uma opinião de alguém do testemunho.

@sedubois você pode usar a função como usaria a função normal renderToString. Mas ele retorna uma Promise que resolve para uma string, em vez de retornar uma string diretamente.

Então, usá-lo ficaria assim:

var react = require("react");
var renderAsync = require("react-render-async");
var MyComponent = require("./MyComponent");

renderAsync(react.createElement(MyComponent,{some:"props"}).then(function(html){
    console.log(html);
});

Em teoria, ele deve ser capaz de renderizar qualquer componente de reação existente da mesma forma que o renderToString normal faz. A única diferença é que os componentes assíncronos também são suportados.

@nmaro Você está certo. Isso seria útil.

Qual é o status disso?

@firasdib Ainda estamos esperando alguns desenvolvedores de reação comentarem, eu acho.

+1

Os problemas de indexação do Google exigem SSR e toda a bagagem descrita abaixo é para nós lidarmos e, portanto, esse problema é muito importante para que o React tenha sucesso como uma biblioteca geral.

Acho que toda a renderização assíncrona não é necessária se o JavaScript fornecesse uma maneira adequada de aguardar uma promessa. Então poderíamos passar um parâmetro indicando o tempo ou não queremos que o carregamento de dados seja síncrono (para SSR) ou assíncrono (para navegador da web real). A lógica de carregamento de dados pode iniciar uma promessa no construtor de qualquer componente React e a espera pode ser feita em componentWillMount.

No entanto, pelo que entendi, os desenvolvedores do React mostraram dúvidas sobre a utilidade do componentWillMount e recomendaram que o construtor fizesse todo o trabalho. De qualquer maneira funcionará.

Algumas pessoas argumentam que tornar os componentes em visualizações puras resolve o problema. Estão parcialmente corretos. Infelizmente, isso não funciona no caso mais genérico em que não sabemos quais dados carregar até que a árvore de renderização seja concluída.

O cerne do problema é que a função de renderização pode ter lógica para selecionar quais componentes adicionar ao DOMTree. É isso que torna o React tão poderoso e tão mal adequado para renderização no lado do servidor.

IMO a verdadeira solução é poder registrar todas as promessas de carregamento de dados emitidas com react. Quando todas as promessas de carregamento de dados estiverem concluídas, o react nos chama de volta com o resultado da renderização. Isso tem várias implicações:

  • o DOMtree será renderizado várias vezes. É como um sistema ativo que "se estabelece" de um estado instável de carregamento de dados para um estado "carregado" estável.
  • existe o risco de que o sistema não chegue a um estado "carregado" estável em um sistema com bugs. Tempo limite tem que ser introduzido para lidar com isso.

O resultado é um uso muito mais "intensivo de computação" da biblioteca no lado do servidor com um processo que não é rápido.

Assim, ainda estou pensando em uma solução alternativa que comprometa o caso genérico em favor de fazer as coisas :)

Nesse meio tempo, temos que resolver o SSR no ambiente de carregamento de dados assíncrono.

  1. As chamadas de carregamento de dados devem ser derivadas da URL de solicitação (react-router é um exemplo). Colete todas as promessas de carregamento de dados em uma única lista de promessas.
  2. Use Promise.all(), que em si é uma promessa. Quando esta promessa for concluída, passe os dados carregados para a função de renderização do servidor.

Esse modelo não está vinculado a uma arquitetura de design específica. Parece que o flux/redux pode se beneficiar um pouco, mas não tenho certeza, pois abandonei o modelo redux em meu aplicativo.

O problema neste modelo é o item #1 acima. Um aplicativo simples conhece todas as chamadas de carregamento de dados. Em uma aplicação complexa com "módulos" independentes este modelo requer algum tipo de replicação da árvore de componentes original do React e até mesmo renderização lógica.

Não vejo como o SSR pode funcionar no projeto complexo sem repetir a lógica já escrita na função render(). O fluxo pode ser uma solução, mas não experimentei para ter certeza.

A implementação do item nº 1 é vaga. No exemplo do react-router, temos como roteador para nos retornar os componentes de nível superior que pertencem a essa rota. Acho que podemos instanciar esses componentes:

app.use(function(req, res, next) {
    match({ routes, location: req.url }, (error, redirectLocation, renderProps: any) => {
    if (error) {
        res.status(500).send(error.message)
    } else if (redirectLocation) {
        res.redirect(302, redirectLocation.pathname + redirectLocation.search)
    } else if (renderProps) {
        // You can also check renderProps.components or renderProps.routes for
        // your "not found" component or route respectively, and send a 404 as
        // below, if you're using a catch-all route.
        console.log("renderProps", renderProps)
        for (let eachComp of renderProps.components) {
            // create an instance of component
            // ask component to load its data
            // store data loading promise in a collection
            console.log("eachComp: ", eachComp)
        }
        res.status(200).send(renderToString(<RouterContext {...renderProps} />))
        } else {
        res.status(404).send('Not found')
        }
    })
});

Portanto, componentes de nível superior não são mais componentes "puros". Esses componentes agora desempenham um papel de controlador de nível superior, devido à capacidade de acionar o carregamento de dados. O modelo de fluxo não o torna melhor. Ele simplesmente move a rota para a associação da função de carregamento de dados para um módulo diferente.

Infelizmente, continuar com o carregamento de dados dessa maneira para os filhos dos controladores superiores resulta em uma duplicação do gráfico de objeto criado para fins de renderização de reação. Para manter as coisas simples (se possível), temos que colocar todo o carregamento de dados no nível superior do controlador.

Isso é o que eu vejo.

Se eu percebesse essas implicações do SSR, teria pensado duas vezes sobre a utilidade do React para aplicativos indexáveis ​​do Google. Talvez o flux seja uma solução, mas o flux torna a aplicação em um nível mais complicado do que uma simples aplicação React. Eu estive lá. Em vez da função simples de carregamento de dados, tenho que perseguir minha lógica em vários arquivos. No nível teórico, parecia realmente muito bom até eu ter um projeto em execução. Novas pessoas tiveram dificuldade para começar. Não há esse problema depois de retirar o redux da base de código. Eu pensei que era a resposta para todas as complexidades do design da interface do usuário :)

A partir do React Conf desta semana, após o lançamento do React Fiber (React 16), eles estão planejando trabalhar no seguinte (supostamente para o React 17):

screen shot 2017-03-16 at 15 55 51

Significa que os métodos de ciclo de vida React assíncronos podem estar chegando?

Bem, o material da fibra não é principalmente sobre uma re- renderização mais rápida/suave? No servidor, renderizamos apenas uma vez, então não tenho certeza se essas alterações terão algum efeito na renderização do lado do servidor.

@VanCoding atualizou minha mensagem acima, que não estava clara. Eu quis dizer seus planos para depois que a fibra acabar.

Para pessoas que chegam aqui como eu, acabei criando uma implementação personalizada usando react que pode ajudar.

https://github.com/siddharthkp/reaqt

a chave é asyncComponentWillMount que é executado apenas no servidor. ele está disponível apenas para componentes de ponto de entrada, não para todos os componentes para evitar os problemas mencionados aqui.

@siddharthkp Eu realmente não vejo o que seu componente tenta resolver? Se ele suporta apenas asyncComponentWillMount para o componente Top Level Application React, por que devo usar sua biblioteca? Eu poderia apenas resolver essa promessa fora e depois chamar render? Ou eu perdi alguma coisa?

@fkrauthan

Eu poderia apenas resolver essa promessa fora e depois chamar render?

Você está certo, isso é exatamente o que ele faz.

O apelo é que você obtém ssr assíncrono (+ divisão de código + hmr) com um comando. Vi muita gente não implementar o ssr porque não é super fácil, e essa foi a motivação.

ele suporta apenas asyncComponentWillMount para o componente React de aplicativo de nível superior

  1. Não precisa de um componente de aplicativo. Você pode buscar dados para cada página/tela/ponto de entrada. (promovendo vários pontos de entrada aqui )

  2. O padrão é intencional, se cada componente da árvore quiser seus próprios dados, podemos acabar incentivando um padrão ruim de desempenho. Buscar dados no componente de nível superior e passá-los para os filhos como adereços os mantém sob controle.

@siddharthkp como seu repositório melhora em relação ao Next.js (que também foi apresentado no React Conf BTW)?

https://github.com/zeit/next.js

De qualquer forma, a discussão sobre bibliotecas de terceiros é IMHO off-topic, o que nos interessa é o suporte dentro do próprio React.

discussão sobre bibliotecas de terceiros é IMHO off-topic, o que nos interessa é o suporte dentro do próprio React.

Eu concordo completamente. Pode discutir questões específicas em questões de reaqt

Um caso de uso que tenho para isso é usar ícones vetoriais react-native e react-native-vector. Eu quero que a prop de um componente seja determinada pelos dados recebidos de uma promessa

const AsyncUser = props => fetchUser()
  .then(data => (
     <User {...data} />
   ))

Se suportado, acho que isso tornaria muitos códigos complexos mais fáceis de entender e possivelmente abriria novos padrões assíncronos.

nós temos o mesmo problema, eu quero fazer o ssr, não apenas pesquisar os props de previsão, mas também o conteúdo buscado pela ação em componentWillMount, como posso esperar que os adereços sejam preparados e, em seguida, renderize os adereços para string.

Se alguém achar isso útil, escrevi uma biblioteca chamada react-frontload que permite vincular uma função de retorno de promessa a um componente para ser executada quando o componente for renderizado, tanto no servidor quanto no cliente.

A função é executada 'sincronicamente' na renderização do servidor (bem, não realmente ;-), mas a renderização do componente final é bloqueada até que seja resolvida), e de forma assíncrona como normal na renderização do cliente, e não é executada novamente no cliente se a página foi apenas renderizado pelo servidor. Há também mais opções que você pode especificar para um controle mais detalhado de quando ele é executado, por componente.

É realmente útil para carregamento de dados - praticamente o caso de uso que escrevi para resolver. Experimente! 😄

https://github.com/davnicwil/react-frontload

<AsyncComponent 
  delayRendering={LoadingComponent}
> 
   {/*return a promise that returns a component here*/}
</AsyncComponent>

Pense em toda a renderização condicional que você não teria que fazer com a abordagem acima porque sabe que tem seus dados

Entrando nessa. Eu acho que ainda é um recurso essencial que falta ao React. Concorde também que, mesmo que libs de terceiros possam cobrir esse assunto, isso deve vir diretamente de dentro.

Eu criei uma implementação https://github.com/timurtu/react-render-async

Interface de componente assíncrona muito boa, @timurtu.

...Ei pessoal, só queria entrar aqui, pois tenho várias coisas que se relacionam muito diretamente com este tópico, e venho acompanhando este tópico há muito tempo:

  • Por um lado, considerando o SSR, acho que a busca de dados no nível da rota faz mais sentido - eis o porquê:
    Busca de dados do Redux-First Router: resolvendo o caso de uso de 80% para Middleware assíncrono
  • renderização de servidor com Redux agora é estupidamente simples, aqui está um guia passo a passo com Redux-First Router de como fazer isso como você nunca viu antes: Server-Render like a Pro /w Redux-First Router em 10 etapas
  • por último, para fazer a divisão de código corretamente, você também precisa fazer as duas coisas (ou seja, SSR + Splitting) . Acontece que fazer as duas coisas simultaneamente tem sido um grande problema , alcançado com sucesso por poucos, e nunca até agora ter um pacote geral para ajudar a comunidade. React Universal Component + babel-plugin-universal-import + webpack-flush-chunks (e extract-css-chunks-webpack-plugin ) são uma família de pacotes que realmente resolvem esse problema. E pela primeira vez. Basicamente, ele permite renderizar componentes de forma síncrona no servidor, mas, mais importante, transporta os fragmentos exatos renderizados para o cliente para uma renderização síncrona inicial também no cliente. Você ainda obtém folhas de estilo CSS por meio do plug-in do webpack. Isso é diferente do que @timurtu compartilhou e muitos desses componentes, que exigem o carregamento de main.js , análise, avaliação, renderização e, finalmente, alguns segundos depois, solicitando as importações dinâmicas. Estas são soluções para resolver flash de conteúdo sem estilo (FOUC), mas não para incorporar em sua página os pedaços exatos renderizados no servidor. Universal -- o nome da minha família de pacotes -- permite que você faça isso com muito mais rapidez até o interativo (TTI) de maneira síncrona que funciona como aplicativos da web tradicionais. Você pode aprender mais sobre isso neste artigo: React Universal Component 2.0 & babel-plugin-universal-import

Eu sei que não abordei os prós/contras do nível de rota versus componente , mas o primeiro link se concentra principalmente nisso. Basicamente, tudo se resume ao nível do componente ser ruim para o SSR se você tiver componentes aninhados cujos dados devem ser buscados em sequência (em vez de paralelo). E se você tiver corretamente apenas uma busca para fazer por rota, bem, é melhor formalizar o contrato anexando suas dependências de dados a uma entidade de rota, em vez de fazê-lo em componentDidMount . Muitos outros acima chegaram a esta mesma conclusão. Redux-First Router formaliza isso muito bem ao longo das linhas do tipo de "contratos" que tornam o Redux tão agradável de se trabalhar.

O Redux-First Router renova muitos paradigmas antigos, mas surpreendentemente se encaixa como uma peça que faltava no ecossistema Redux. Se você sempre quis um roteador nativo para o fluxo de trabalho do Redux, experimente o Redux-First Router. É relativamente novo, e sou um novato no código aberto, mas é algo em que trabalhei basicamente por um ano e ganhou muita força nos 2 meses em que foi lançado. As pessoas estão adorando. Eu realmente espero que você dê uma olhada e dê uma chance :).

Aqui está também o artigo de lançamento que explica como é uma solução muito melhor para o Redux do que o React Router, e como ele acerta a visão de uma maneira que o também excelente Redux-Little Router infelizmente perdeu:
Pré-lançamento: Redux-First Router — Um passo além do Redux-Little-Router

A principal diferença entre RLR e RFR é que RFR despacha um tipo de ação diferente por rota (ou seja, alteração de URL), em vez de sempre apenas LOCATION_CHANGED . Isso permite alternar as alterações de URL como tipos exclusivos em seus redutores, assim como qualquer outra ação. Pode parecer pouco, mas faz uma grande diferença. Por isso, não requer componentes <Route /> , algo que é desnecessário na terra do Redux. Há também uma enorme lista de recursos que o Redux-First Router suporta, desde busca de dados na rota thunks até React Native, React Navigation, divisão de código, pré-busca e muito mais.

Adoraria ouvir os pensamentos das pessoas.

@faceyspacey obrigado, acho que resolve o problema que eu estava tendo, que é quando você deseja definir props com base em dados assíncronos, por exemplo, react-native-vector-icons pode retornar um ícone como uma promessa. É definitivamente uma maneira diferente de ver as coisas, mas reduz componentWillMount ou redux X_GET_START , X_GET_END e X_GET_ERROR padrão de ação que vincula componentes assíncronos ao estado, quando você realmente deseja fazer essa solicitação apenas uma vez para renderizar o componente. Se você está fazendo coisas como dados de pesquisa, ter estado e usar redux provavelmente faz mais sentido

@faceyspacey Eu vejo o que você está dizendo sobre o nível de rota, mas não seria melhor ter componentes menores que dependam apenas da busca dos dados de que precisam enquanto renderizam tudo ao seu redor de forma síncrona?

Melhor? Onde? No React Native? Em grandes organizações como o Facebook em seus aplicativos React Native?

Talvez no React Native. Quando há SSR, na minha opinião, não há debate. O que você quer fazer simplesmente não é possível sem várias buscas por solicitação, a menos que, novamente, você limite a um componente que possa fazer a busca. Nesse ponto, é melhor formalizar o contrato com as entidades de rota.

Quanto a React Native e SPAs, com certeza. Mas testar componentes que têm deps de dados integrados é uma dor. A Apollo tem algumas coisas aqui, mas pela última vez que verifiquei, eles tinham uma longa lista de tarefas para obter o teste de componentes emparelhados de dados realmente correto, ou seja, sem interrupções. Eu prefiro uma configuração em que você tenha uma loja que seja fácil de configurar e simular. Você pode reutilizar como você faz isso em todos os seus testes. A partir daí, os componentes de teste são renderizações síncronas simples com captura de instantâneos. Depois de adicionar dependências de dados nos componentes, testar o React se torna menos intuitivo. Mas as pessoas fazem isso e automatizam. Há menos de um padrão e agora está mais próximo do código ad-hoc personalizado. É mais produtivo ter uma loja que pode ser preenchida de forma assíncrona de uma maneira muito óbvia, em vez de ter estratégias potencialmente diferentes para cada componente assíncrono que precisa ser preenchido.

Eu não sou contra o nível de componente se não houver SSR e você tiver sua estratégia de teste perfeita e fora do caminho. Se você é mega corp, faz sentido porque todo desenvolvedor não precisa tocar o nível de rota superior e potencialmente quebrá-lo para outros. É por isso que o FB foi pioneiro nessa rota com Relay e GraphQL. O fato é que apenas 100 empresas no mundo estão realmente neste barco. Portanto, vejo o colocation como mais hype do que realidade para a maioria das pessoas/aplicativos/empresas. Uma vez que você pega o jeito da coisa do nível de rota e tem um pacote que faz isso muito bem como o Redux-First Router , então você tem uma abordagem muito menos complicada de desenvolver para equipes cujos membros podem tocar no nível de rota . Basta conferir a Demo na página inicial do codesandbox:

https://www.codesandbox.io

Basicamente, uma vez que você veja como é feito, adoraria ouvir sua opinião. Tenha em mente que este é um SPA. Portanto, você terá que revisar o repositório de demonstração ou o clichê para um exemplo de SSR. Mas este é um bom começo.

Acho que a maneira mais fácil será suportar o método de renderização assíncrono (render retorna uma promessa). Ele permitirá a renderização dos componentes filhos da espera do componente raiz e, claro, o resultado da renderização será uma promessa. Eu implementei algo assim no preact aqui https://github.com/3axap4eHko/preact-async-example

@faceyspacey Obrigado pela resposta detalhada acima. Eu realmente gosto da ideia do Redux-First Router, ele definitivamente resolve todos os meus problemas e torna as coisas muito mais transparentes e limpas do que tínhamos antes.

Muito obrigado!

@raRaRa que bom que gostou! ...um dos maiores desafios é que não houve uma boa abstração para busca de dados em nível de rota até agora. React O React v3 tinha callbacks, e o v4 tem um padrão excelente (particularmente com o pacote react-router-config ), mas não é óbvio e não é de primeira classe no v4. É uma reflexão posterior. Mais importante, porém, ele não vive no Redux. Portanto, é apenas agora pela primeira vez que os usuários do redux têm uma abstração completa de "nível de rota".

Acho que agora com o RFR, vamos repensar a importância do "nível da rota" versus o "nível do componente". Coisas como Relay e Apollo criaram muito hype em torno do emparelhamento de dependências de dados com componentes, mas se você não estiver usando esses 2, provavelmente acabará em um mundo de dor em algum momento fazendo coisas em manipuladores de ciclo de vida .

Dito isto, eventualmente, o RFR terá uma integração de nível de rota com o Apollo. Há apenas muito poucos benefícios para fazer as coisas no nível do componente, menos as raras exceções mencionadas acima. Pelo menos 80% do tempo (e talvez 100% em seu aplicativo), você pode determinar todos os dados necessários com base na rota. Se não for possível, é provável que você esteja derivando dados em sua "camada de visualização" que muito bem poderiam ser repensados ​​para serem interpretados pelo URL. Portanto, mesmo com ferramentas como o Apollo, você começa a se envolver em antipadrões problemáticos que provavelmente acabarão causando dor ao deixar o "estado" viver em nenhum outro lugar além da camada de visualização. Ou seja, o estado necessário para realizar essas buscas de dados. Muitas vezes você precisa refinar os parâmetros exatos usados ​​para buscar seus dados. Então você pega state e props e transforma o 2 para obter os parâmetros antes de buscar os dados. São coisas que agora vivem na camada de visualização e estão sujeitas a seus mecanismos de re-renderização (ou seja, quando os manipuladores de ciclo de vida são chamados). Todo aplicativo em um ponto ou outro acaba com os dados não buscados quando deveriam ou buscados em resposta a props não relacionados recebidos/alterados. E em todos os aplicativos, você precisa escrever código extra para se proteger contra adereços não relacionados que foram alterados. Ou seja, adereços que não afetam os parâmetros usados ​​para buscar dados.

Quanto ao React Native + Apollo, o problema é menor porque você não tem SSR e, portanto, potencial para buscas de dados sequenciais acontecendo no servidor. No entanto, você realmente precisa de busca de dados em nível de componente quando as telas são tão pequenas. Você geralmente sabe o que cada "cena" precisa em termos de seus dados. Não é como um painel de painel da web de desktop com um milhão de gráficos, várias tabelas, etc. Esse caso de uso é talvez o maior local principal onde o nível de componente começa a fazer sentido. Mas mesmo lá você pode especificar todos os seus requisitos para todos esses widgets em um só lugar e buscá-los via Apollo. Ainda não pensei o suficiente, mas a ideia básica é anexar seus requisitos de dados do GraphQL a uma rota e, em seguida, em seus componentes, conectar-se aos dados como qualquer outro dado Redux. Então, mais especificamente, a ideia é eliminar o connect do react-redux e o equivalente do Apollo. Eu só quero um, e queremos que seja mais plano. O uso do Apollo requer a desestruturação de dados aninhados mais profundamente para obter acesso ao que você realmente deseja em seus componentes. A visão é uma maneira que se parece com o Redux normal, além das especificações do graphql no nível da rota.

É um pouco prematuro falar sobre isso, já que mal analisei, mas se o nível de rota é a abordagem correta para seu aplicativo, Apollo + GraphQL não deve ser uma exceção.

Por fim, o teste é muito mais fácil se todas as suas dependências de dados e trabalho assíncrono forem separados dos componentes. A separação de interesses é um grande impulsionador de produtividade quando você pode testar seus componentes sem ter que se preocupar com a busca de dados. É claro que em seus testes você deve preencher sua loja fazendo essas buscas assíncronas com antecedência, mas com elas separadas de seus componentes, você pode formalizar facilmente esse trabalho em algumas funções auxiliares setup todos os seus testes usam para pré- preencha a loja antes de testar como os componentes são renderizados :)

@faceyspacey concordo plenamente. Mas devo admitir que não experimentei o Apollo nem o GraphQL, definitivamente os verificarei e verei como eles se encaixam nos meus casos de uso.

Estou muito acostumado a escrever aplicativos da web usando o padrão MVC, onde basicamente puxo todos os dados e dependências do controlador. Em seguida, injeto os dados nas visualizações ou na lógica de negócios. Como você disse, isso torna o teste muito mais fácil, pois a visualização não é acoplada à busca de dados.

Eu vi aplicativos MVC onde a parte View está buscando dados e fazendo lógica de negócios, e é terrível. Ambos não tenho ideia de quais dados serão buscados porque estão ocultos em alguma exibição/componente e o teste se torna mais difícil/impossível.

Para mim, o Redux-First Router é muito parecido com a parte Controller no MVC, além das rotas. O que faz tanto sentido :)

Obrigada.

No final do dia, nossa nova pilha é esta:

M: Redux
V: Reagir
C: Redux-First Router (em breve será renomeado para "Rudy" )

Quando o React foi lançado (e particularmente o Redux), todos nós parecíamos querer jogar fora a antiga sabedoria do MVC, como se de alguma forma isso significasse que poderíamos nos livrar completamente das dores no desenvolvimento de aplicativos anteriores. Mas acho que, em última análise, nada mais é do que simplesmente não ter as ferramentas construídas para apoiá-lo. No final das contas, ainda nos beneficiamos muito do MVC, mesmo em aplicativos reativos que priorizam o

Pode funcionar um pouco diferente na nova pilha do cliente em primeiro lugar em relação aos MVCs tradicionais do lado do servidor, mas basicamente é a mesma separação de 3 preocupações.

Quanto à diferença de interação/comportamento entre os 3, é basicamente que no passado o controller pegava modelos em uma fase inicial e então renderizava as views com eles; e agora é o controlador (RFR) renderizando imediatamente uma exibição (escolhida por meio de um "modelo de interface do usuário", ou seja, estado Redux) e, em seguida , renderizando novamente a exibição uma segunda vez quando o controlador encontrar modelos de domínio (também armazenados no estado Redux).

Olá pessoal, vejam este link. aqui está o exemplo de renderização do lado do servidor de dados https://github.com/bananaoomarang/isomorphic-redux

@harut55555 idk pode ser apenas eu, mas isso parece muito código para fazer uma solicitação de obtenção e uma solicitação de postagem

Alguma mudança? Liberado 16 reagem e a maioria das soluções não funciona

Eu acho que a abordagem atual é usar redux com ferramentas como redux observable ou redux thunk

Meu caso de uso

<App>
    <Page>
        <AsyncModule hre="different.com/Button.react.js" /> downloaded from external url on server or client
    </Page>
</App>

O problema é que não sei antes de renderizar o App que tipo de componentes terei

Por que não baixar dados/conteúdos em vez de um componente apenas curioso?

Quando? Tenho páginas dinâmicas e o componente pode ou não ser

Parece que você quer renderização condicional https://reactjs.org/docs/conditional-rendering.html

Eu acho que é problema de roteamento, reagir deve apenas renderizar a página, não gerenciar e solicitar dados de renderização

Usar @graphql do Apollo torna o carregamento de dados super simples, e a API baseada em componentes do React-Router v4 torna o roteamento composto simples. Ambos são ortogonais ao estado do aplicativo no Redux.

Para fazer a renderização de streaming de passagem única que aguarda buscas de dados, deve ser possível que render() retorne uma promessa (no cliente você faz como agora, apenas no servidor você retorna uma promessa).

Então @graphql pode simplesmente retornar uma promessa para o render() após os dados serem carregados; todo o resto é simplesmente React.

Alguns meses atrás eu dei uma palestra no JSConf Iceland que descreve os próximos recursos de renderização assíncrona no React (veja a segunda parte): https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react -16.html. Trata-se da busca de dados do lado do cliente.

Agora @acdlite deu uma palestra sobre como os mesmos conceitos podem ser aplicados para habilitar a espera assíncrona de dados no servidor em componentes React, e progressivamente liberando a marcação quando ela estiver pronta: https://www.youtube.com/watch?v= z-6JC0_cOns

Espero que você goste de assistir a essas palestras! Acho que em um ano ou mais poderemos fechar essa questão e ter uma estratégia oficial para isso.

Eu me lembro do anel. Nós nos casando planejando um futuro juntos. Por favor, termine com isso agora e venha me ver. O tempo trabalhou sempre amor

É possível usar ReactDOMServer.renderToStaticMarkup para percorrer a árvore, colocar um marcador onde ele alcança dependências assíncronas (normalmente carregando dados de uma API) e continuar percorrendo a árvore à medida que essas dependências são resolvidas, até que toda a árvore seja resolvida, e, em seguida, faça um ReactDOMServer.renderToString final para realmente renderizar para HTML, que será síncrono porque todas as dependências agora estão resolvidas.

Eu fiz essa abordagem em react-baconjs : render-to-html.js . Foi inspirado por um comentário que @gaearon fez em outra edição recomendando tal abordagem.

@steve-taylor sim, isso funciona. É também a solução alternativa que uso no react-frontload, que é uma solução de propósito mais geral para isso, que funcionará em qualquer aplicativo React.

Como você diz, essencialmente, é apenas fingir renderização assíncrona executando a lógica de renderização síncrona duas vezes e aguardando todas as promessas de carregamento de dados que você atingiu na primeira renderização para resolver antes de executar a segunda.

Funciona bem o suficiente, apesar de obviamente ser um pouco inútil (a saída da primeira renderização é mais do que apenas promessas, é também o HTML real que é jogado fora). Será incrível quando a verdadeira renderização de servidor assíncrona chegar ao React.

@davnicwil bom trabalho! Eu pensei em generalizar a solução SSR assíncrona específica. O react-frontload lida com profundidade assíncrona ilimitada? Perguntando porque você disse “duas vezes”.

@steve-taylor parabéns! Sim, se por profundidade você quer dizer profundidade do componente na árvore, é ilimitado. Ele permite que você declare uma função de carregamento de dados no próprio Component (com um componente de ordem superior) e, quando isso for encontrado na primeira renderização, ele será executado e a promessa será coletada.

O que não funcionará é quando houver mais componentes filho que também carregam dados de forma assíncrona que seriam renderizados dinamicamente dependendo do resultado, se isso fizer sentido. Significa apenas que o aplicativo deve ser estruturado para que não haja esse tipo de aninhamento, o que descobri na prática não ser realmente uma limitação enorme. Na verdade, de muitas maneiras, é melhor em termos de UX porque, é claro, a lógica assíncrona aninhada significa solicitações seriais e tempos de espera mais longos, enquanto o achatamento significa solicitações paralelas.

Dito isso, o problema de aninhamento (de fato, talvez todo esse problema de renderização de servidor assíncrono) pode ser resolvido com as coisas do Suspense chegando ao React em um futuro próximo. 🤞

Para sua informação, começamos a trabalhar nisso.

https://reactjs.org/blog/2018/11/27/react-16-roadmap.html#suspense -for-server-rendering

Maravilhoso @gaearon!

@davnicwil react-baconjs suporta profundidade ilimitada.

@gaearon Gostaria de saber se isso tornaria possível o suporte a observáveis ​​em create-subscription, para que eu possa converter um JSX de disparo observável em um componente de reação?

Eu adoraria ter esse recurso por dois motivos. Primeiro, no meu aplicativo, o estado é armazenado em um web worker. Portanto, enquanto a recuperação de bits desse estado que são exigidos pelo componente é uma operação assíncrona, leva 5ms e não faz sentido para o React renderizar qualquer coisa enquanto aguarda os dados. Em segundo lugar, no momento não há uma maneira universal de converter um observável em um componente: se o seu observável emitir o primeiro valor de forma síncrona, o que você faz é se inscrever para obter esse valor, cancelar a inscrição e se inscrever novamente para ouvir as alterações (essa é a Replay exemplo de assunto em documentos observáveis ​​de criação). E se ele emitir o primeiro valor de forma assíncrona, você inicialmente renderizará null e ouvirá as alterações.

@steve-taylor react-frontload agora também suporta profundidade ilimitada de aninhamento de componentes, com o lançamento 1.0.7 .

Espero que esta biblioteca seja útil para algumas pessoas que acessam este tópico - se você estiver procurando por uma solução de carregamento de dados assíncrono que funcione na renderização de cliente/servidor, com um esforço mínimo de integração, verifique-a.

Encontramos esse problema antes, pois nosso aplicativo foi construído com React Hooks e, em seguida, criamos um pacote react-use-api para resolvê-lo, que é um gancho personalizado que busca dados de API e suporta SSR. Espero que o pacote possa ajudar as pessoas necessitadas.

No entanto, ainda estamos ansiosos para esperar por uma solução oficial, assim como o react-frontload diz, não há uma maneira interna de esperar pelo carregamento de dados assíncronos quando a renderização começar no momento.

Esta página foi útil?
0 / 5 - 0 avaliações