React: Prise en charge du rendu du serveur asynchrone (en attente des données avant le rendu)

Créé le 24 juin 2014  ·  139Commentaires  ·  Source: facebook/react

Cela faciliterait sérieusement le processus de construction de quelque chose d'isomorphe si componentWillMount pouvait retourner une promesse et cette réaction retarderait le rendu jusqu'à ce que cette promesse soit résolue. J'ai vu des tentatives de faire quelque chose comme ça dans react-router et rrouter, mais donner cette responsabilité à chaque composant au lieu d'un module de routeur aurait plus de sens pour moi.

Component API Server Rendering Backlog Feature Request

Commentaire le plus utile

Il y a quelques mois, j'ai donné une conférence à JSConf Iceland qui décrit les prochaines fonctionnalités de rendu asynchrone dans React (voir la deuxième partie) : https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react -16.html. Il s'agit de la récupération de données côté client.

Maintenant, @acdlite a https://www.youtube.com/watch?v= z-6JC0_cOns

J'espère que vous apprécierez de regarder ces discussions ! Je pense que dans un an environ, nous pourrons peut-être clore ce problème et avoir une stratégie officielle pour cela.

Tous les 139 commentaires

La principale raison (je crois) pour laquelle cela n'existe pas déjà est que du côté client, vous voulez toujours afficher une sorte d'indicateur de chargement au lieu de différer le rendu. (Cela rendrait également le code beaucoup plus complexe, mais nous pouvons probablement nous en occuper.)

Il y a 2 cas que j'ai du mal à résoudre sans ça :

  • Côté serveur, si vous souhaitez récupérer les données avant le rendu, vous ne pouvez pas déléguer la récupération des données aux composants car vous n'avez pas les informations sur le composant qui sera rendu
  • Côté client, la première fois que vous montez votre application après avoir reçu du code html pré-rendu, même si vous avez une sorte de cache à partir des données récupérées sur le serveur, vous voudrez peut-être utiliser une méthode async pour récupérer ces données, et cela empêcherait de réagir de réutiliser le html.

react-async résout ces problèmes avec les fibres et le cache. Cela fait l'affaire, mais à mon avis, ce ne sont que des solutions _hackish_ pour résoudre un problème qui ne peut être résolu que dans le noyau.

Colorez-moi mal informé à ce sujet, @fdecampredon dit que componentWillMount est async et vous ne retournez rien immédiatement, qu'est-ce que React est censé rendre jusqu'à ce qu'il n'y ait rien ? Si c'est le cas, pourquoi ne pas simplement ne rien renvoyer dans le rendu s'il n'y a pas encore de données ? (Ouais, je reçois côté serveur) De plus, que devrait-il se passer si les accessoires changent avant le déclenchement de componentWillMount ?

Personnellement, il semble qu'il soit erroné d'envoyer des requêtes asynchrones pendant componentWillMount moins que le composant ne soit vraiment une boîte noire isolée et implémente également un indicateur de chargement. Autant que je sache, les composants React ne doivent pas être confondus avec des instances de POO plus conventionnelles. Dans le meilleur des cas, un composant React est un outil pour visualiser les données dans les accessoires, s'il est interactif, alors peut-être aussi l'état. C'est une vue, pas une vue et un modèle.

À mes oreilles, cela ressemble au problème, les composants React ne devraient pas être ceux qui envoient les requêtes asynchrones, vous récupérez toutes les données et lorsque ces données sont prêtes, vous n'appelez alors React.renderComponent . Même solution côté client et côté serveur. Vous avez également la possibilité d'abandonner avec un résultat de votre choix si une requête asynchrone échoue.

N'hésitez pas à me rejeter si j'ai mal compris quelque chose, mais il semble que vous traitez les composants React comme une vue et un modèle, alors qu'il semble qu'ils ne soient que la vue.

Colorez-moi mal informé à ce sujet, @fdecampredon dit que componentWillMount est async et que vous ne retournez rien immédiatement, qu'est-ce que React est censé rendre jusqu'à ce qu'il n'y ait rien? Si c'est le cas, pourquoi ne pas simplement ne rien renvoyer dans le rendu s'il n'y a pas encore de données ? (Ouais, je reçois côté serveur) De plus, que devrait-il se passer si les accessoires changent avant que componentWillMount ne se déclenche ?

Je dois avouer que je n'ai pas pensé à tous les cas ^^.
Cette fonctionnalité ne serait utile que la première fois que nous montons le composant de niveau supérieur, et sur le serveur, il est vrai que sinon, dans la plupart des cas, vous voudriez afficher un indicateur de chargeur.

Personnellement, il semble qu'il soit erroné d'envoyer des requêtes asynchrones pendant componentWillMount à moins que le composant ne soit vraiment une boîte noire isolée et implémente également un indicateur de chargement. Autant que je sache, les composants React ne doivent pas être confondus avec des instances de POO plus conventionnelles. Dans le meilleur des cas, un composant React est un outil pour visualiser les données dans les accessoires, s'il est interactif, alors peut-être aussi l'état. C'est une vue, pas une vue et un modèle.

D'une manière ou d'une autre, vous souhaiterez qu'un composant de "niveau supérieur" soit capable de récupérer des données, comme cela est fait dans l' exemple Flux .
Dans cet exemple, les choses sont assez simples car récupérer la liste des tâches est une opération synchrone, si ce n'était pas le cas, et en cas de pré-rendu sur le serveur, nous rendrions une première fois sans données (et perdrions le pré-rendu balisage du serveur).

Dans le cas d'une application simple avec un ensemble de données affiché par une hiérarchie de vues, il n'y a toujours pas trop de problème, vous pouvez précharger les données tout en conservant la propriété synchrone de votre magasin.
Maintenant, dans le cas d'une application composée de plusieurs modules que vous réutilisez dans votre application, j'aimerais pouvoir traiter ces modules comme des applications distinctes capables de "s'abonner" sur différents magasins (qui seraient responsables de la récupération des données).

Peut-être que je comprends mal les choses, mais certaines discussions/échantillons sur le Web me font penser qu'il manque quelque chose quelque part :

  • Dans certains exemples, il me semble que @petehunt a essayé de réaliser quelque chose de similaire.
  • react-nested-router fait la promotion d'un mécanisme similaire dans willTransitionTo , et certaines discussions me donnent l'impression que personne n'est venu avec une solution appropriée.
  • RRouter fournit également une sorte de mécanisme pour extraire les données au fur et à mesure que le composant est rendu/monté.
  • et enfin réagir-async comme je l'ai dit plus tôt.

@fdecampredon Pour être clair, le but de willTransitionTo dans react-nested-router n'est pas de charger des données, en particulier parce que renvoyer une promesse à partir de cette méthode bloquera en effet le rendu de la nouvelle interface utilisateur, ce que vous ne voulez pas faire à moins que vous n'y soyez absolument obligé.

@fdecampredon Tout le monde essaie toujours de comprendre les choses, donc cela ne me surprendrait pas si personne n'a de réponse définitive. Mais je suppose que les développeurs de Facebook ont ​​dû le rencontrer eux-mêmes à quelques reprises.

Une mise à jour pour ceci? Je viens de commencer à explorer React et je me suis immédiatement heurté à cela. De nombreuses personnes recommandent React comme solution de choix pour la création d'applications isomorphes, mais tant que cela n'est pas résolu, je pense que cela ne peut tout simplement pas faire le travail.

À mes oreilles, cela ressemble au problème, les composants React ne devraient pas être ceux qui envoient les demandes asynchrones, vous récupérez toutes les données et lorsque ces données sont prêtes, vous n'appelez alors React.renderComponent. Même solution côté client et côté serveur. Vous avez également la possibilité d'abandonner avec un résultat de votre choix si une requête asynchrone échoue.

N'hésitez pas à me rejeter si j'ai mal compris quelque chose, mais il semble que vous traitez les composants React comme une vue et un modèle, alors qu'il semble qu'ils ne soient que la vue.

Si cela est vrai, alors React n'est rien de plus qu'une solution de modélisation / couche de vue légèrement différente. Et ce serait dommage car il y a un tel potentiel. Je comprends bien @fdecampredon quand il évoque ces applications complexes composées de plusieurs modules. React serait parfait pour cela.

Je ne pense pas que cette approche signifierait traiter un composant comme une vue et un modèle. Si vous regardez l'architecture Flux, ils considèrent les composants React comme des _controller-views_ qui non seulement affichent des données (à partir de magasins), mais invoquent également des actions basées sur les interactions de l'utilisateur. Et les actions mettent ensuite à jour les magasins (= modèle). Pour moi, cela ressemble à une architecture MVC évidente.

Le problème vient de l'action initiale qui remplit le magasin. C'est assez simple du côté client où l'action peut être invoquée à partir de la méthode componentDidMount() comme recommandé. Du côté du serveur, par contre, nous avons vraiment besoin d'un endroit spécial et d'un mécanisme bienveillant qui retarderait le rendu jusqu'à ce que l'action soit terminée et que les magasins soient remplis.

Pour le moment, il me semble que le seul moyen est React-async/Fibers + Flux. La bonne chose à propos de Flux est que nous n'avons pas besoin de cache artificiel pour transporter les états des composants du serveur au client (comme c'est fait dans l'exemple d'origine de react-async), nous pouvons simplement initialiser les magasins, puis les envoyer au client avec le balisage html (voir cet exemple ).

Mais cette solution est en effet _hackish_.

Je ne pense pas qu'il faille nécessairement que componentWillMount soit asynchrone; Je ne suis même pas sûr que cela doive être un événement du cycle de vie. Le vrai problème est que côté serveur, il n'y a aucun moyen d'analyser l'arborescence des composants tant que tout n'a pas été rendu dans une chaîne.

Ma solution idéale pour résoudre ce problème serait d'autoriser le "rendu" qui ne ferait que construire l'arbre des composants, puis nous serions en mesure de parcourir l'arbre pour trouver des composants qui ont besoin de données supplémentaires, nous permettre de charger de manière asynchrone plus de données et " ce sous-arbre de composants, puis une fois que nous sommes prêts à vider le balisage, nous permet de convertir cet arbre en chaîne.

Cela reproduit ce que nous sommes capables de faire dans le navigateur : avoir un DOM virtuel que nous pouvons restituer à notre guise. La différence est que dans le navigateur, les mises à jour du DOM peuvent être implicites. Sur le serveur, nous devons être explicites sur le moment où nous rendons une chaîne afin que nous puissions effectuer des mises à jour du DOM virtuel en fonction des données asynchrones.

Ma solution idéale pour résoudre ce problème serait d'autoriser le "rendu" qui ne ferait que construire l'arbre des composants, puis nous serions en mesure de parcourir l'arbre pour trouver des composants qui ont besoin de données supplémentaires, nous permettre de charger de manière asynchrone plus de données et " ce sous-arbre de composants, puis une fois que nous sommes prêts à vider le balisage, nous permet de convertir cet arbre en chaîne. - @mridgway

Ouais, ce serait bien. Actuellement, le rendu du composant deux fois côté serveur peut être utilisé comme solution de contournement.

Je veux faire référence à react-nexus comme exemple de ce que je voudrais être pris en charge dans React. Il s'agit essentiellement d'une réécriture du fonctionnement de mountComponent , sauf qu'il construit l'arborescence des composants sans réellement le monter sur le DOM ou écrire une chaîne. Cela vous permet de parcourir l'arborescence des composants et de déclencher des méthodes asynchrones pendant la construction de l'arborescence. Le problème avec cette implémentation telle qu'elle est, c'est qu'elle jette ce premier arbre, puis appelle React.renderToString toute façon, alors qu'il serait bien de prendre cet arbre de pré-rendu et de le rendre/monter.

Je suis prêt à travailler sur cela dans le noyau, mais j'aurais besoin de quelques pointeurs. Je pense que l'une des exigences est de faire en sorte que ReactContext ne

@mridgway Si je ne me trompe pas, le ReactContext global pourrait être une chose compatible et disparaîtra en 0.14. Mais je _pourrais_ me tromper.

@gaearon Oui, c'est le sens que j'ai eu de l'obsolescence de withContext.

:+1 : Utilisation de react-router pour ce guichet automatique. L'approche de @mridgway semble très raisonnable.

Une approche légèrement différente de ce problème est de savoir comment gérer le chargement asynchrone de morceaux de code produits par un bundler (tel que webpack).

Comme indiqué dans ce ticket - https://github.com/rackt/react-router/issues/1402 - le problème avec la prise en charge du rendu asynchrone est que le rendu initial semble être nul car les morceaux pertinents n'ont pas encore été chargés dans le premier pass de rendu, résultant en un <noscript> à la racine du DOM et un échec de la somme de contrôle. Tout se met en place rapidement par la suite, mais cela se traduit par un scintillement dans l'interface utilisateur lorsque vous travaillez localement et pire sur le terrain, surtout si les morceaux à télécharger sont d'une taille raisonnable (disons > 30 Ko)

La possibilité de diviser les grosses applications en plusieurs morceaux est importante pour nous et après avoir résolu le problème de récupération des données (nous avons utilisé un routeur réactif et des routes imbriquées qui peuvent être traversées avant le rendu sur le serveur pour récupérer les dépendances de données) c'est la dernière pièce du casse-tête nous empêchant de passer complètement à une solution React pour notre front-end.

@anatomic Ce n'est pas la responsabilité de React, c'est votre travail de fragmenter de manière appropriée et de différer le rendu jusqu'à ce que tous les fragments nécessaires aient été chargés. Pour le dire différemment, si l'un de vos composants a une dépendance sur une bibliothèque externe, c'est évidemment votre problème de vous satisfaire avant d'essayer de l'utiliser, React ne pourrait pas le faire même s'il essayait, donc la même chose s'applique à tous les niveaux.

N'hésitez pas à mettre en œuvre des stratégies alternatives qui pourraient mieux vous convenir, disons <WaitFor for={MyAsyncLoadedCompSignal} until={...} then={() => <MyAsyncLoadedComp ... />} /> . Mais ceux-ci sont intrinsèquement opiniâtres et ne sont pas quelque chose que React devrait ou même a besoin d'offrir, il est donc préférable de laisser la communauté.

Il est préférable de garder les éléments asynchrones en dehors de la portée des composants React, voici un exemple :

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();

Voir Webpack Code Splitting , React.js Routing from Scratch et -routing (à venir)

@syranide Je vais continuer à travailler dessus, mais je ne pense pas que ce soit aussi binaire que vous l'avez dit ci-dessus. Nous utilisons react-router, ce qui peut introduire des problèmes dans le mix car le routeur est un composant plutôt que de rester en dehors de l'environnement React.

Si nous appliquions l'approche <WaitFor ... /> , nous obtiendrons sûrement toujours un DOM différent lors du premier rendu, ce qui provoquera toujours le scintillement/la disparition du contenu ?

Si nous faisions le approche, nous aurons sûrement toujours un DOM différent sur le premier rendu, ce qui provoquera toujours le scintillement/la disparition du contenu ?

Si vous ne voulez pas de scintillement (certains le font), il suffit d'attendre que tous les morceaux dont vous dépendez aient été chargés avant le rendu, webpack fournit ce prêt à l'emploi avec require.resolve .

PS. Oui, react-router et tout ce que vous utilisez d'autre complique sûrement la solution, mais ce n'est toujours pas le problème de React à résoudre.

Si vous ne voulez pas de scintillement (certains le font), il suffit d'attendre que tous les morceaux dont vous dépendez se soient chargés avant le rendu, webpack fournit ce prêt à l'emploi avec require.resolve.

Je vais examiner cela, ma compréhension de require.resolve était qu'il s'agissait d'une recherche synchrone de l'ID d'un module et n'impliquait pas un voyage vers le serveur ? Nous utilisons require.ensure pour gérer notre chargement de morceaux.

En regardant à nouveau notre code, je pense que nous pouvons contourner le problème en faisant croire à react-router qu'il est rendu sur le serveur, mais en suivant ensuite l'approche côté client standard :

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'));
});

Je vais examiner cela, ma compréhension de require.resolve était qu'il s'agissait d'une recherche synchrone de l'ID d'un module et n'impliquait pas un voyage vers le serveur? Nous utilisons require.ensure pour gérer notre chargement de morceaux.

Désolé, oui je voulais dire require.ensure . Le rappel n'est exécuté que lorsque toutes les dépendances sont satisfaites, il suffit donc de mettre render/setState à l'intérieur.

Ok, c'est un peu comme ça que nous faisions, merci pour vos réponses. Cela ressemble à quelque chose qui doit être traité dans react-router, je vais donc poursuivre la discussion là-bas - désolé si ce n'était pas le bon endroit pour avoir cette conversation !

Vous avez raison de dire que le require.ensure est la voie à suivre, je suppose que notre problème ultime était qu'il devrait être lié à l'itinéraire actuellement visité, donc directement lié au routeur. Avec react-router basé sur un composant qui le lie à l'arbre de rendu. Sans mon hack ci-dessus, nous nous battons pour trouver un moyen de voir l'arbre avant que tout ne soit chargé de manière asynchrone (ou en dupliquant la logique de routage pour permettre le chargement des morceaux pertinents au niveau supérieur).

Vous avez raison de dire que le require.ensure est la voie à suivre, je suppose que notre problème ultime était qu'il devrait être lié à l'itinéraire actuellement visité, donc directement lié au routeur. Avec react-router basé sur un composant qui le lie à l'arbre de rendu. Sans mon hack ci-dessus, nous nous battons pour trouver un moyen de voir l'arbre avant que tout ne soit chargé de manière asynchrone (ou en dupliquant la logique de routage pour permettre le chargement des morceaux pertinents au niveau supérieur).

Je ne suis pas intimement familier avec react-router, mais j'imagine toujours qu'il s'agit simplement d'un cas de setRoute(...) => require.ensure([], function() { require(...); setRoute(...); }) ce qui n'est vraiment pas pratique, donc vous introduisez un autre niveau d'indirection. Une carte des itinéraires et le chargeur async require.ensure pour chacun. Écrivez un assistant function asyncSetRoute(...) { loadRoute(route, function() { setRoute(...); }} , maintenant vous appelez asyncSetRoute place et il reportera la mise à jour du routeur jusqu'à ce que tout soit prêt.

Pseudo-code et genre de générique, mais cela me semble être l'approche globale. Peut-être que react-router devrait fournir cela, peut-être pas... peut-être qu'il est idéalement fourni en tant qu'assistant externe, je ne suis pas sûr.

Donc, après des heures de recherche, je viens de confirmer que le rendu côté serveur est _impossible _ à moins de tout alimenter de haut en bas (?).

Solutions possibles à court terme :
A. Pré-remplir un magasin et rendre le chargement côté serveur synchrone

B. Alimentez tout par le haut comme une seule entrée de données après avoir récupéré asynchrone votre seul objet de données.

re: 'A'. Cela ne fonctionnera que si vous savez déjà à quoi ressemblera votre structure de rendu. J'ai besoin de le rendre afin de connaître mes différentes dépendances de collections/modèles/api. De plus, comment faire en sorte que la récupération soit asynchrone sur le client, mais synchronisée sur le serveur, sans avoir deux API différentes ?

re: 'B'. En gros le même problème que ci-dessus. Les gens doivent-ils créer de petits JSON de dépendance pour chacune de leurs routes ?

Existe-t-il d'autres solutions à court terme en attendant une solution prise en charge par React ? Des fourches ou des plugins pour les utilisateurs ? https://www.npmjs.com/package/react-async ?

@NickStefan

Je ne comprends pas le problème. :-(

  1. Utilisez Flux ou un conteneur de type Flux (Alt, Redux, Flummox, etc.) où les magasins ne sont pas des singletons.
  2. Définissez des méthodes statiques telles que fetchData sur vos gestionnaires de route qui renvoient des promesses.
  3. Sur le serveur, faites correspondre la route aux composants, récupérez fetchData et attendez qu'ils se terminent avant le rendu. Cela préremplira votre instance de magasin Flux ou Redux. Notez que l'instance de magasin n'est pas un singleton - elle est liée à une requête particulière, les requêtes restent donc isolées.
  4. Lorsque les promesses sont prêtes, effectuez un rendu synchrone avec l'instance de magasin que vous venez de préremplir.

Voici un bon tutoriel pour Redux décrivant cette approche : https://medium.com/@bananaoomarang/handcrafting -an-isomorphic-redux-application-with-love-40ada4468af4

@gaearon, je m'excuse si je vous ai confondu. Merci pour votre réponse. D'après votre liste, il semble que j'aie raison de supposer que la solution à la dépendance des données du serveur consiste à ne définir les besoins en données que sur votre composant racine (méthodes statiques / article auquel vous vous êtes lié). Si vos dépendances de données sont définies à la racine, il est beaucoup plus facile de pré-remplir les magasins, etc.

C'est une bonne pratique de flux, mais n'est-ce pas potentiellement limitatif ? Si j'ajoute un petit composant au bas de votre arborescence de vue qui nécessite des données très différentes, je dois ensuite modifier les dépendances de données à la racine.

Ce que je demande, c'est un moyen pour les composants profondément imbriqués de définir les besoins de données asynchrones.

Si je dois ajouter leurs besoins au composant racine, ne suis-je pas en train de coupler la racine aux besoins de ce sous-composant ?

@NickStefan avec -routing , par exemple, la récupération de données asynchrone ressemble à ceci :

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));

Ces solutions fonctionnent, mais uniquement parce que toutes les données de pré-extraction sont liées au routeur. Ce n'est pas un problème dans la plupart des cas (c'est logique, l'URL définit ce qui doit être sur la page et donc quelles données sont nécessaires) mais en général c'est une limitation. Vous ne pourrez jamais créer un composant réutilisable autonome (c'est-à-dire un flux Twitter, des commentaires, un calendrier) qui gérerait tout par lui-même et tout ce que vous aviez à faire était simplement de l'insérer dans la hiérarchie des composants. La seule façon que j'ai rencontrée qui rend cela possible est la réaction asynchrone, mais c'est à peu près un hack.

Je suppose que les composants React ne sont tout simplement pas destinés à être utilisés de cette façon, ce sont toujours plus de vues que de _controller_-views. Une toute nouvelle bibliothèque devra probablement voir le jour sur la base de React.

Mon rêve est qu'un jour nous puissions écrire un CMS complet en utilisant React, quelque chose comme Wordpress, Drupal ou Modx. Mais au lieu d'extraits HTML/PHP, nous composerons le site Web à partir de composants React.

@NickStefan

D'après votre liste, il semble que j'aie raison de supposer que la solution à la dépendance des données du serveur consiste à ne définir les besoins en données que sur votre composant racine (méthodes statiques / article auquel vous vous êtes lié). Si vos dépendances de données sont définies à la racine, il est beaucoup plus facile de pré-remplir les magasins, etc.

Si je dois ajouter leurs besoins au composant racine, ne suis-je pas en train de coupler la racine aux besoins de ce sous-composant ?

Pas exactement à la racine—au niveau du gestionnaire de route. Il peut y avoir de nombreux gestionnaires d'itinéraires dans votre application. De plus, des bibliothèques telles que React Router prennent en charge les gestionnaires de routes imbriqués . Cela signifie que votre gestionnaire de routes imbriqué peut définir une dépendance et que la correspondance du routeur contiendra un tableau de gestionnaires de routes hiérarchiques correspondants, vous pouvez donc les Promise.all . Est-ce que ça a du sens?

Ce n'est pas aussi granulaire que "chaque composant peut déclarer une dépendance", mais j'ai constaté que dans la plupart des cas, limiter la récupération de données aux gestionnaires de routes (supérieurs et imbriqués) est tout ce que je veux. J'ai des composants purs ci-dessous qui acceptent les données via props donc ils ne savent même pas qu'il est récupéré. Il est logique qu'ils ne puissent pas le demander.

L'approche « chaque composant peut déclarer une dépendance » n'est pas pratique à moins que vous n'ayez une sorte de solution de traitement par lots des demandes. Imaginez si <User> déclare qu'il a besoin d'un appel d'API, et maintenant vous affichez une liste de 100 <User> s, voudriez-vous attendre 100 requêtes ? Ce type de granularité des exigences de récupération des données ne fonctionne que lorsque vous disposez d'une solution de traitement par lots des demandes. Si cela vous convient, Relay est exactement cela. Mais vous auriez besoin d'un backend spécial pour cela.

@NickStefan Nous ne prenons actuellement pas en charge la récupération asynchrone par composant. Nous aimerions l'ajouter. Ce numéro suit nos progrès, même si personne n'y travaille activement et qu'il faudra une restructuration majeure, ce sera donc un certain temps.

@gaearon

L'approche « chaque composant peut déclarer une dépendance » n'est pas pratique à moins que vous n'ayez une sorte de solution de traitement par lots des demandes.

Après réflexion, je suis d'accord avec toi. Ce que je cherchais n'est pas vraiment pratique.

J'ai d'abord pensé que même tes 100exemple serait correct avec la discipline d'équipe (c'est-à-dire faites juste un <UsersContainer> qui fait 1 demande pour tous les 100). Mais ce n'est pas « personnalisable » d'avoir simplement une convention. Il est probablement préférable d'appliquer toutes les dépendances à la racine ou au routeur. Même Relay vous oblige en quelque sorte à déclarer les dépendances à la racine avec ses trois conteneurs différents (RelayContainer, RelayRootContainer, RelayRouter, etc.). Cela prouve en quelque sorte que le seul moyen consiste essentiellement à « élever » vos déclarations de dépendance dans l'arborescence.

Salut!
Y a-t-il des mises à jour à ce sujet ?

+1

Je maintiens que si vous pensez vraiment à cela, vous ne voulez pas faire cela. Au départ, je pensais que je voulais que React fasse un rendu asynchrone. Mais d'autres dans ce fil m'ont convaincu du contraire.

Même dans Relay, vous devez essentiellement « élever » vos dépendances dans l'arborescence avec les déclarations de conteneur racine, les conteneurs de relais, etc. Le rendu synchrone est la voie à suivre.

Le rendu asynchrone peut être un cauchemar. Je parle d'expérience de travail sur une base de code d'entreprise avec une épine dorsale qui a été piratée pour faire des mises à jour individuelles dans les images d'animation de demande.

D'accord. J'avais l'habitude de penser que nous en avions besoin, mais c'est en fait à l'envers que la vue spécifie les données à charger. La vue est fonction de l'état de l'application, lui-même fonction de la requête (et éventuellement de l'état de l'utilisateur, s'il y a une session). C'est ce qu'est React; permettre aux composants de spécifier quelles données doivent être chargées va à l'encontre de l'idée du « flux de données à sens unique », IMO.

c'est en fait à l'envers pour que la vue spécifie les données à charger.

Je ne suis pas tout à fait sûr que ce soit vrai. Parfois, le composant est la seule chose qui sait quelles données charger. Par exemple, supposons que vous ayez une arborescence extensible qui permet à un utilisateur de parcourir un graphe massif de nœuds - il est impossible de savoir à l'avance quelles données doivent être chargées ; seul le composant peut le comprendre.

Quoi qu'il en soit, cette discussion pourrait devenir beaucoup plus pertinente si nous poursuivons l'idée d'exécuter du code React dans un travailleur Web (#3092), où l'async est nécessaire pour communiquer à travers le pont.

Dans l'exemple d'une grande arborescence, si je voulais vraiment pouvoir le rendre avec un chemin déjà ouvert côté serveur, j'ajouterais ce chemin à la structure de l'URL. S'il y avait plusieurs composants complexes comme celui-ci, alors je représenterais leurs états avec les paramètres GET. De cette façon, ces composants bénéficient de tous les avantages du SSR : ils sont explorables, peuvent être parcourus à l'aide de l'historique, les utilisateurs peuvent partager des liens vers un nœud en leur sein, etc. Désormais, nous avons également un moyen pour le serveur de déterminer les données dont il a besoin. à récupérer pour donner une réponse.

update Je confonds mal un graphique avec un arbre, mais je pense toujours que l'état de la vue d'un utilisateur sur ce graphique devrait être représenté par la structure de l'URL. De plus, je ne savais pas que vous travailliez réellement sur React. Si vous travaillez sur un framework pour intégrer une couche de données avec la vue de manière isomorphe, c'est génial. Mais je pense que nous pouvons convenir que cela dépasse le domaine de la vue et que React ne devrait pas jouer le rôle d'un contrôleur de pile complète.

Je n'ai pas lu tout le fil donc désolé si cela a déjà été discuté.

Une chose qui aiderait vraiment serait s'il y avait une méthode react-dom/server qui "démarrait/instancierait" simplement un composant et déclencherait des méthodes de cycle de vie mais laisserait le développeur décider quand il est prêt à rendre le composant à une chaîne html . Par exemple, le développeur peut utiliser setTimeout ou quelque chose de plus fiable pour attendre la fin des méthodes asynchrones.

C'est le "hack" que j'utilise avec redux en ce moment pour y parvenir:

  // 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)
    }
  })

Le problème avec cette approche est que le deuxième ReactDOM.renderToString déclenche à nouveau toutes les méthodes componentWillMount , ce qui entraîne une récupération de données inutile.

Salut les gars! est-ce quelque chose en cours d'élaboration ? Je pense que cette question prend de plus en plus d'importance. J'ai récemment créé une bibliothèque qui étend en quelque sorte les connect de react redux à un dataConnect où vous pouvez également spécifier les exigences en matière de données du conteneur. Ces exigences de données sont ensuite regroupées au runtime et interrogées avec GraphQL en une seule requête. Je pense que c'est l'avenir des spécifications de données car elles favorisent la composabilité et l'isolation et assurent également une récupération plus performante. (tous les concepts vus sur Relay)

Le problème avec ce qui précède est le rendu côté serveur. Étant donné que je ne peux pas analyser de manière statique les exigences de données avec lesquelles je vais me retrouver, actuellement, je devrais effectuer un rendu une fois pour obtenir le bundle à interroger à partir de la base de données, masquer le magasin redux, puis effectuer un nouveau rendu pour obtenir la chaîne finale . Le problème est similaire à celui de @olalonde .

Ce serait fantastique de pouvoir déclencher des actions et des mises à jour de l'arborescence des éléments de réaction et d'obtenir le résultat de la chaîne DOM à la demande. Voici comment je l'imagine :

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

Une autre option consiste simplement à le laisser se mettre à jour à volonté comme il le serait du côté client, en ayant tous les crochets présents et fonctionnant comme didMount. Mais au lieu de muter le DOM, il ne ferait que muter la représentation du dom virtuel et serait également rendu en chaîne à la demande.

Qu'en pensez-vous? Quelque chose à considérer ou je le vois complètement faux?

Salut tout le monde. Je suis abonné à ce numéro depuis un an environ. À l'époque, je pensais que je devais pouvoir spécifier les données qui devaient être chargées à partir des composants, car les composants étaient ma principale unité de modularité. Ce problème était vraiment important pour moi, et je pensais qu'il serait certainement résolu dans la prochaine version de React.

Depuis lors, j'ai développé un respect beaucoup plus profond pour les idéaux derrière REST/HATEOS, en raison de la simplicité à grande échelle qui émerge dans un système d'applications (navigateurs, crawlers, serveurs d'applications, proxys, CDN, etc.) lorsque ses principes directeurs sont suivi. En ce qui concerne ce problème, _l'URL doit être la seule véritable représentation de l'état de l'application_. C'est lui, et lui seul, qui devrait déterminer quelles informations sont nécessaires pour répondre à une demande. La couche de vue, React, ne devrait pas le déterminer ; il doit être construit sur la base des données. La vue est fonction des données et les données sont fonction de l'URL .

J'ai hésité à lancer cela dans le passé, parce que j'entends toujours que c'est une bonne idée, mais que le monde réel est tout simplement trop complexe pour que cela fonctionne. Et parfois, j'entends des exemples qui me font prendre du recul, et me demandent si je suis trop pédant ou trop idéaliste. Mais en réfléchissant à ces exemples, je trouve inévitablement un moyen raisonnable de représenter l'état de l'application dans l'URL.

  • Votre état a-t-il des paramètres variant indépendamment ? Représentez-les en tant que paramètres de requête distincts.
  • Votre état (ou une partie de celui-ci) est-il trop grand pour tenir dans une URL ? Nommez-le et stockez-le dans une banque de données immuable. Référez-vous-y par nom/ID.
  • Votre état change-t-il si rapidement que vous ne pouvez tout simplement pas tout stocker pour toujours ? Félicitations, vous avez des données volumineuses légitimes, un ressort pour le stockage et commencez à penser à des choses intelligentes à faire avec. Ou, vous pouvez vous détourner et simplement muter vos données avec des requêtes UPDATE, et accepter que vous ne pouvez pas toujours mettre en cache toutes les choses plus tard (*).
  • Avez-vous des vues différentes pour différents utilisateurs, mais servies sur la même URL, par exemple une page d'accueil personnalisée ? Naviguez lors de l'identification de l'utilisateur vers une URL qui inclut l'ID de l'utilisateur.
  • Est-ce que vous construisez sur une ancienne application, où vous n'avez pas la possibilité de casser l'ancienne structure d'URL ? C'est douloureux, je suis dans le même bateau. Redirigez l'ancienne structure d'URL vers une structure d'URL bien architecturée, traduisant les données de session (ou autre) en segments et paramètres de chemin d'URL.
  • Vous avez peu ou pas de contrôle sur l'architecture de votre application ? Ce n'est pas le cas pour lequel React a été conçu, et nous ne voulons pas que React soit mutilé pour le faire s'intégrer dans quelque chose comme une architecture Wordpress / Magnolia / Umbraco mutuellement compatible.

Qu'obtenez-vous pour tout ce travail, que vous n'auriez pas eu autrement ?

  • La possibilité pour un utilisateur d'amener un autre utilisateur au même endroit qu'eux dans l'application, en partageant une URL.
  • La possibilité de naviguer dans l'application à l'aide de tous les outils fournis par le navigateur. Le client standard est dans son élément.
  • La possibilité de proposer une pagination complexe d'une manière simple à suivre pour le client : un lien next dans une réponse. L'API FB graph en est un excellent exemple.
  • Un graphique détaillé des flux de travail de votre utilisateur dans Google Analytics.
  • La possibilité de construire vous-même ledit graphique à partir de rien d'autre que des journaux de demande.
  • Une échappatoire à la route étoilée du chaos : au lieu de faire correspondre toutes les requêtes en tant que app.get("*", theOneTrueClientonaserverEntryPoint) , vous pouvez utiliser votre infrastructure d'application serveur comme prévu. Vous pouvez renvoyer des codes d'état HTTP corrects à partir des requêtes, au lieu de 200 OK\n\n{status: "error"} . La route des étoiles est alléchante, mais elle mène à la folie.

Alors, maintenant que React, notre outil vedette, ne contrôle pas l'opération, comment obtenons-nous nos données ? Comment savons-nous quels composants rendre?

  1. Acheminez vers une fonction d'accès aux données et récupérez les données pour les paramètres de requête pertinents. Répétez jusqu'à ce que vous ayez analysé l'intégralité de l'URL et que vous ayez un objet de contexte complet.
  2. Transformez le contexte en l'objet de réponse le plus simple possible, comme si vous alliez répondre à une requête d'API. Servez-vous une demande d'API ? Ensuite, vous avez terminé et SEC.
  3. Passez cet objet peu complexe, mais peut-être volumineux, au composant de niveau supérieur. À partir de là, c'est la composition des composants React jusqu'en bas.
  4. Rendre en chaîne, répondre. Faites savoir à votre CDN qu'il peut le mettre en cache pour toujours (* à moins que vous n'ayez sacrifié cette option ci-dessus), car le CDN s'identifie par des URL et tous vos états ont une URL. Bien sûr, les CDN n'ont pas de stockage infini, mais ils donnent la priorité.

Je ne veux pas troller ici, mais je pense fermement que le thème central des demandes de ce numéro est erroné et que React ne devrait rien mettre en œuvre pour y répondre, du moins pas pour les raisons que j'ai vues ici plus l'année passée. Certains aspects d'une bonne architecture d'application ne sont pas évidents, et le Web est particulièrement difficile à maîtriser. Nous devons apprendre et respecter la sagesse de nos ancêtres, qui ont construit Internet et le Web, au lieu de sacrifier l'élégance au profit du confort.

C'est ce qui rend React génial : c'est la vue. La meilleure vue. Seule la vue. ??

Devra être en désaccord avec vous @d4goxn. Je n'essaie pas de faire de React plus qu'une couche de vue, et le routage est important mais certainement pas suffisant pour spécifier toutes les exigences en matière de données. En spécifiant les exigences de données au niveau du composant/conteneur, vous ne faites pas de React plus qu'une couche de vue. Cela vous offre simplement une bien meilleure isolation et un meilleur flux de travail pour votre application. Ce n'est pas seulement une simplification du travail mais un besoin d'une grande application. Imaginez que mon application dispose de 30 routes et que je souhaite ajouter un composant de profil utilisateur sur chacune. En suivant une alternative de route où toutes les exigences de données sont spécifiées pour chacune, vous devrez parcourir les 30 routes pour ajouter cette dépendance de données. Quand avec si vous spécifiez vos besoins en données dans un conteneur pour ce composant, il vous suffit d'ajouter le composant où vous le souhaitez. C'est du plug and play. Non seulement cela, mais la requête pour toutes les dépendances de données de composants peut être optimisée. Relay en est un bon exemple, et les discussions à ce sujet l'expliquent bien mieux que moi.

J'ai beaucoup de respect pour la vieille sagesse, mais cela ne devrait pas être une limite pour l'évolution et la création de nouvelles normes, c'est du moins ainsi que je le vois.

Ma proposition est essentiellement de ne pas modifier React, mais d'avoir un moyen « virtuel uniquement » de modifier l'arborescence dom/composants, essentiellement un React qui peut être « exécuté » côté serveur, ce qui, je pense, est assez simple à faire (il suffit de bloquer les actions modifier le DOM). Vous pouvez toujours avoir la mise en cache et avoir un CDN en place. Avec la dynamique croissante des sites et des applications de nos jours, la mise en cache statique aura tendance à se réduire mais c'est un autre sujet 😄

Si la vue spécifie des dépendances sur les données, vous aurez besoin d'un moyen d'optimiser le graphe de dépendances et de le transformer en un nombre minimal de requêtes ; vous ne pouvez pas préparer les données avant de construire la vue, bien que vous puissiez la diviser en deux phases, ce que je comprends du plan dans ce fil. Prenez une collection de composants modérément complexes qui auraient chacun leur propre requête, par exemple ; ce ne sont peut-être pas toutes des instances du même composant, et il n'y a pas de modèle qui pourrait être réduit en une seule requête. Je suppose que c'est ce à quoi GraphQL s'attaque. Vous devrez toujours implémenter ou intégrer des serveurs GQL pour chacun de vos magasins de données. Trier quelles parties de la requête GQL optimisée doivent être gérées par quel magasin de données semble assez complexe, mais ma proposition a aussi une partie de cette complexité.

Dans l'exemple, où il existe un grand nombre de routes nécessitant toutes les mêmes données, je ne verrais pas cela comme une raison pour exclure l'identifiant de cette source de données de l'URL. Je monterais un petit module middleware de récupération de données assez près de la racine de la pile, qui attacherait cet objet utilisateur à un contexte, puis transmettrait le contexte à plus de middleware, en route vers un gestionnaire de route de fin. Le composant racine React pourrait ne pas se soucier de cette partie particulière du contexte, mais il la transmettrait au niveau suivant d'enfants qui pourraient s'en soucier, ou qui auraient leurs propres descendants qui s'en soucient. Si cela introduit un câblage déraisonnablement complexe ou profond, alors quelque chose comme un magasin Flux pourrait être nécessaire, mais c'est un gros sujet en soi.

Séparation et isolement : je ressens votre douleur là-bas. Dans ma proposition, nous avons deux systèmes très différents, transformant les données d'une forme optimisée pour le stockage et la récupération en une forme optimisée pour une simplicité abstraite. Ensuite, mon point de vue transforme cela en une forme qui lui convient, au fur et à mesure qu'il est transmis dans la hiérarchie des composants. Garder ces deux systèmes faiblement couplés tout en ajoutant une fonctionnalité qui nécessite des ajouts aux deux systèmes n'est pas ce que j'appellerais difficile, mais ce n'est pas non plus le chemin de la moindre résistance. Le basculement mental entre la programmation pour un magasin de données et la programmation pour une interface utilisateur dynamique est réel. Nous avions des développeurs backend et frontend séparés, et HTTP servait d'interface entre ces deux paradigmes. Maintenant, j'essaie de l'internaliser dans une seule application, et vous essayez de la déléguer.

Je ne suis pas convaincu que la complexité et l'activité croissantes au sein des applications empêchent l'état du client d'être représenté par un très petit objet, qui fait référence à un ensemble de données beaucoup plus important sur le serveur. Considérez un jeu de tir à la première personne multijoueur massif : il se passe beaucoup de choses rapidement et vous souhaitez transmettre le minimum d'informations nécessaires du client à l'hôte/serveur du jeu. Comment petit pourriez-vous obtenir cela? Une carte de tous les états d'entrée ; un horodatage + plage d'incertitude, un champ ~110 bits pour le clavier, et quelques dizaines d'octets pour l'orientation souris/joystick et casque VR. Le client serait en train de prédire, d'effectuer un rendu et d'effectuer une physique avec optimisme, et le serveur déduirait une énorme quantité d'informations du petit état et de l'historique de l'état du client, et renverrait une assez grande quantité de données pour corriger et mettre à jour tous les clients ( tous les mêmes paramètres pour tous les agents à proximité, les poussées d'actifs), mais ce flux de demandes client pourrait encore s'intégrer confortablement dans des demandes de 2 Ko. Et cela pourrait être une aubaine pour l'architecture de l'application.

Je ne vous demanderai pas de me renseigner sur Relay et GraphQL ; Je ne les ai que superficiellement explorés, avant que leurs API n'atteignent une version stable (sont-elles verrouillées maintenant ?) est un bon plan, alors il est peut-être temps que je les examine à nouveau. J'ai quelques questions difficiles sur cette architecture, mais je suis sur le point de m'éloigner du sujet.

:bières:

PS Je ne voulais pas suggérer que HTTP serait un bon protocole de communication pour un MMOFPS

@d4goxn Je comprends parfaitement votre hésitation et votre scepticisme à ce sujet. Je n'y ai jamais pensé avant de commencer à travailler avec GraphQL et plus tard l'introduction des concepts de Relay. Je vous recommande vraiment d'y jeter un œil. Et la mise en œuvre de GraphQL dans un serveur nouveau ou existant n'est pas aussi difficile que vous pourriez l'imaginer, même si vous disposez de plusieurs magasins de données. Je ne veux pas non plus sortir du sujet, mais c'est une discussion importante.

Je crois que ce niveau d'abstraction est la voie à suivre. Je l'ai utilisé sur Relax CMS et le flux de travail et l'isolement sont un bonheur de travailler avec. Non seulement cela, mais les données demandées ne sont ni plus ni moins ce dont l'interface utilisateur a besoin, et cela se fait automatiquement en collectant les données dont chaque composant a besoin et en les fusionnant. Ceci est fait par Relate http://relax.github.io/relate/how-it-works.html si vous souhaitez vérifier. Avec cela, je peux également inclure n'importe quel composant n'importe où dans mon application et être sûr qu'il fonctionnera comme un bloc indépendant de mon application.

La seule chose à comprendre est le rendu côté serveur. Après avoir parcouru le code source de react, je pense qu'il existe peut-être déjà une solution comme je l'ai décrit, sinon je pourrais travailler sur une solution si quelqu'un de l'équipe de réaction est d'accord avec cela.

@d4goxn J'oserai également discuter avec vous :) Votre message d'origine contient une déclaration selon laquelle la vue est fonction des données et que les données sont fonction de l'URL . Je n'appellerais pas cela exactement révélateur. Cela s'applique à peu près à n'importe quel site Web ou à n'importe quelle application Web qui n'est pas complètement aléatoire (il se pose la question de savoir si, par exemple, l'état des fenêtres de dialogue ouvertes doit également faire partie de l'URL). Comme nous avions tous des mathématiques à l'école, nous savons que cette fonction peut être composée. Ainsi, la déclaration réelle pourrait être que ce que

Pour moi, la question principale est de savoir comment construire une telle fonction .

L'approche que vous suggérez ressemble beaucoup aux bonnes vieilles applications MVC côté serveur (par exemple, Spring MVC est un bon exemple). L'URL actuelle active la méthode de contrôleur correspondante qui _fait la logique métier_. Il récupère toutes les données requises et les transmet à la vue. Mon problème avec ceci est le suivant : lorsque vous regardez la page Web générée, il s'agit d'une sorte de hiérarchie de composants (pas nécessairement de composants React). Ce que vous devez faire est de créer ou de générer la hiérarchie à partir de l'URL. Mais il faut le faire deux fois ! Tout d'abord, vous devez décoder la hiérarchie dans le contrôleur pour savoir quelles données vous devez récupérer, et deuxièmement, vous devez décoder la hiérarchie de la vue pour... eh bien... restituer la vue réelle. Je ne pense pas que ce soit une approche très _DRY_.

Je ne suis pas très familier avec Spring MVC mais j'utilise très souvent un autre framework MVC (framework PHP appelé Nette) qui résout ce problème d'une manière très similaire à la façon dont j'aimerais utiliser React. Ce framework prend également en charge les composants. L'idée est que pour chaque composant (par exemple un formulaire) vous définissez une fabrique dans votre code qui se charge d'instancier le composant et surtout de _charger toutes les données nécessaires_. Un tel composant peut alors être inclus n'importe où dans la vue. Ainsi, lorsque vous écrivez le code HTML et créez la _hiérarchie des vues_, vous pouvez simplement insérer un composant et la fabrique sous-jacente se charge de l'initialisation nécessaire. Le composant se comporte comme un petit contrôleur indépendant, il a son propre cycle de vie, il gère ses dépendances et il peut même être paramétré depuis la vue ce qui augmente sa réutilisabilité.

J'ai également utilisé cette approche avec React côté client et cela semble très viable. Les composants React ont été désignés depuis le début par _controller-views_ et c'est ainsi que je les utilise. J'ai mes composants _controller_ qui sont responsables de la récupération des données, puis j'ai mes composants _view_ qui ne se soucient que des visuels. La page entière est composée de composants des deux types. Ils sont bien découplés et facilement réutilisables.

Mon application n'est pas isomorphe (ou universelle comme ils l'appellent aujourd'hui) car je n'en avais pas besoin mais la pile que j'utilise devrait en être capable (et je crois qu'à part Relay qui est encore expérimental, cette pile a parcouru le chemin le plus long en la matière). Pour info, voici à quoi ça ressemble :

  • L'outil principal est react-router . Il permet d'accrocher des requêtes de données asynchrones sur chaque URL et il fonctionne à la fois côté client et côté serveur. Formellement parlant, cette approche souffre du problème que j'ai mentionné plus tôt. Ces composants _controller_ ne sont pas possibles. Mais ce qui compte ici, c'est la conception de react-router . Il permet de définir à la fois la _hiérarchie des données_ et la _hiérarchie de la vue/des composants_ au même endroit et les définitions de route réelles sont également hiérarchiques. Il se sent très _React-like_.
  • L'état entier (les données) est géré avec redux . Outre tous les autres avantages, il conserve l'ensemble de l'état dans un seul objet, ce qui signifie qu'il est très simple et naturel de créer l'objet sur le serveur et de l'envoyer au client. De plus, grâce à l'approche connect() , il n'est pas nécessaire de transmettre toutes les données du niveau supérieur à l'aide de props (ce qui serait absolument fou compte tenu de la taille actuelle de mon application).
  • Une autre pièce du puzzle est la resélection, ce qui nous permet de garder l'état aussi petit que possible. Et augmente également considérablement le facteur de découplage.

Ce n'est toujours pas parfait, mais c'est un progrès significatif par rapport à ce qui était disponible lorsque je suis tombé sur ce fil pour la première fois. Un exemple de mise en œuvre peut être trouvé ici .

J'ai aussi presque tout le travail isomorphe, sauf que j'ai besoin d'un moyen d'accomplir le rendu côté serveur lorsque les récupérations de données sont au niveau du composant. Sans ajouter à l'argument philosophique pour le moment, j'utilise depuis longtemps avec succès Vanilla React (plus le routage local) avec des composants récupérant leurs propres données dans componentDidMount. Je n'aime vraiment pas l'idée de maintenir une structure redondante pour définir mes dépendances de composant et de données - qui est déjà définie dans mon arborescence de composants, et ici j'ai peut-être besoin de plusieurs passes de rendu qui incorporent les données telles qu'elles sont récupérées de manière asynchrone, jusqu'à ce que le composant complet arbre a été résolu.

En ce moment, je voulais juste souligner l'importance croissante de cela pour la réaction isomophique, imo. Je vais essayer de trouver une solution en attendant. Merci.

@decodeman au cas où vous seriez intéressé, pour Relate, j'ai effectué une réaction minimale pour obtenir les dépendances de données des composants https://github.com/relax/relate/blob/master/lib/server/get-data -dépendances.js. De cette façon, je peux obtenir toutes les dépendances de données -> récupérer les données -> réagir au rendu en chaîne.

@decodeman , FWIW, nous avons utilisé avec succès l'approche de double rendu (comme à laquelle vous faites allusion) dans une grande application de production. Il est inspiré de cet exemple de redux-saga , mais l'approche générale devrait bien fonctionner avec tout type de chargement asynchrone. Il vous suffit de déplacer le chargement vers componentWillMount . Pour aider à le garder gérable, nous avons également des composants d'ordre supérieur qui prennent en charge les scénarios de chargement courants (par exemple, vérifiez la prop x et chargez si nil.

Je pense également qu'il serait très important de pouvoir charger des données dans des composants et d'avoir une sorte de méthode de rendu paresseux. Peut-être pour rendre les deux parties heureuses, créez une nouvelle méthode appelée asyncRenderToString et ajoutez un nouvel événement de "cycle de vie" appelé asyncOnLoad ou quelque chose comme ça. Cela ne sera appelé que si vous appelez une méthode asyncRender. En termes de duplication de code entre le serveur et le client, les développeurs peuvent résoudre ce problème en déplaçant simplement le code de chargement commun dans une méthode distincte et en appelant cette méthode à partir de asyncOnLoad et componentMount . Détecter si asyncOnLoad est effectué devrait probablement utiliser une promesse renvoyée (si undefined est renvoyé/méthode non définie, il doit appeler directement les événements de cycle de vie applicables, puis restituer), sinon il attend la résolution de la promesse.

Je pense qu'une solution comme celle-ci résoudrait tous les moyens piratés que nous devons utiliser en ce moment pour un rendu correct côté serveur. Y compris, par exemple, permet un rendu facile côté serveur des applications de réaction avec le routeur de réaction v4 (qui n'utilise plus une configuration de route centralisée qui est actuellement le seul moyen de faire fonctionner les applications isomorphes)

Cela a été résolu son flux appelé check out react stream.

Le jeudi 3 novembre 2016, Florian Krauthan [email protected]
a écrit:

Je pense aussi qu'il serait très important de pouvoir charger des données dans
composants et ont une sorte de méthode de rendu paresseux. Peut-être pour faire les deux
côtés heureux de créer une nouvelle méthode appelée asyncRenderToString et d'ajouter un nouveau
événement "cycle de vie" appelé asyncOnLoad ou quelque chose comme ça. Cette volonté
être appelé uniquement si vous appelez une méthode asyncRender. En termes de code
duplication entre le serveur et le client cela peut être corrigé par les développeurs
de simplement déplacer le code de chargement commun dans une méthode distincte et de l'appeler
méthode de asyncOnLoad et componentMount. Détecter si asyncOnLoad est
done devrait probablement utiliser une promesse retournée (si undefined est
renvoyé/méthode non définie, il doit appeler directement le
événements du cycle de vie, puis rendu) sinon il attend que la promesse
résout.

Je pense qu'une solution comme celle-ci résoudrait toutes les manières difficiles que nous devons utiliser
pour le moment pour un rendu correct côté serveur. Y compris par exemple permettre
rendu facile côté serveur des applications de réaction avec le routeur de réaction v4
(qui n'utilise plus une configuration de route centralisée qui est actuellement la
seul moyen de faire fonctionner les applications isomorphes)

-
Vous recevez ceci parce que vous êtes abonné à ce fil.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/facebook/react/issues/1739#issuecomment-258198533 ,
ou couper le fil
https://github.com/notifications/unsubscribe-auth/ATnWLUIEJw4m1Y3A4oGDOBzP6_ajDcqIks5q6g4_gaJpZM4CHAWq
.

@yamat124 si je recherche le flux de réaction, je ne trouve que ce projet : https://github.com/aickin/react-dom-stream

Au moins sur la base de la documentation, il n'y a aucun moyen d'avoir un événement de cycle de vie pour le préchargement des données qui retarde le prochain appel de rendu pour les sous-enfants jusqu'à ce que la promesse soit résolue. Ou tu parles d'autre chose ?

Encore une fois, ce sont tous des moyens très hacks. Il nécessite que le routage soit indépendant de l'arborescence des composants. Et le composant "principal" inclus dans l'itinéraire doit connaître toutes les données qui doivent être chargées (fonctionne 99% du temps mais pas toujours). Les deux seraient obsolètes si react a un rendu de composant retardé.

Je suis d'accord, c'est probablement la chose que je déteste le plus en ce moment à propos de réagir. Je déteste quand les gens essaient de défendre le framework de ne pas avoir cette fonctionnalité en disant que cela peut être fait manuellement, bien sûr que cela peut, comme tout ce qui concerne le développement de logiciels, mais cela ne veut pas dire qu'il le devrait. Le fait qu'il y ait des tonnes de modules là-bas prenant soin si cette chose précise devait faire remarquer par elle-même que c'est quelque chose qui devrait être résolu à partir du cadre lui-même.

Next.js a un async getInitialProps pour effectuer le rendu côté serveur, malheureusement juste encore besoin de l' enfant de l' appel getInitialProps des parents tout l'arrière de façon à la racine. Le client Apollo GraphQL permet également de parcourir toute l'arborescence pour rassembler les exigences de données côté serveur puis de tout renvoyer. Exemple de Next + Apollo : https://github.com/sedubois/realate.

Ce serait formidable d'avoir des méthodes de cycle de vie des composants React asynchrones pour combiner ces éléments.

Je viens d'apprendre la SSR cette semaine, j'espère que ce que j'écris a du sens 😄

/cc @nkzawa @stubailo

BTW componentWill/DidMount sont déjà asynchrones, cela aide-t-il ?

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

@sedubois est-ce que React attend que la promesse se résolve avant de rendre ? Sinon, ça n'aide pas vraiment !

@olalonde ça parait : 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 Notez que, dans votre exemple, componentWillMount

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

renvoie undefined et une promesse, mais les captures d'écran montrent que React n'attend pas (il n'a pas pu) la promesse, car il restitue le texte Loading... .

Edit : corrigé.

@sedubois @polytypic Cela entraînera également l' ignorance silencieuse de toutes les erreurs

@polytypic @dantman Je ne sais pas trop ce que l'on entend par "la promesse n'est pas du tout traitée". Il est attendu par l'opérateur await . Vous devez essayer de détecter l'erreur, j'ai mis à jour l'exemple (en utilisant une signature de chargement/d'erreur similaire à ce que fait Apollo).

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

Et le rendu :

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

@sedubois Lorsque vous async une fonction renvoie implicitement une promesse, tout throw dans la fonction se transforme implicitement en un rejet de cette promesse au lieu d'un rejet de la pile. Cela signifie que componentWillMount renvoie maintenant une promesse à son appelant.

React ne fait rien avec la valeur de retour, donc votre fonction renvoie maintenant une promesse s'attendant à ce que quelqu'un fasse quelque chose avec elle, mais elle est simplement rejetée à la place.

Parce qu'il n'est pas géré, cela signifie que tout rejet de cette promesse ne sera détecté par aucun code dans React et aucune erreur non gérée n'apparaîtra dans votre console.

Et même faire un essai..catch ne vous protégera pas parfaitement de cela. Si setState renvoie une erreur à partir de votre capture ou si vous faites une faute de frappe dans la capture, cette erreur disparaîtra silencieusement et vous ne vous rendrez pas compte que quelque chose est cassé.

@sedubois En fait, une méthode async semble toujours renvoyer une promesse en JavaScript (enfin, le futur JavaScript). J'étais confus en pensant aux problèmes d'attente asynchrone en C#. _Désolé pour le bruit !_

Néanmoins, les captures d'écran montrent clairement que React n'attend pas que la promesse soit résolue. Il appelle render immédiatement après avoir appelé componentWillMount et restitue le texte Loading... . Ensuite, il appelle à nouveau render après l'appel à setState . Si React attendait que la promesse soit résolue, il ne rendrait pas du tout le texte Loading... . Au lieu de cela, il attendrait que la promesse soit résolue et n'appellerait alors que render , ce qui est le genre de comportement dont il s'agit : pouvoir effectuer des E/S async pendant le rendu côté serveur.

@dantman @polytypic merci 👍 Oui, des changements sont certainement nécessaires dans React pour attendre la réponse et gérer correctement les erreurs. Peut-être qu'ajouter quelques await ici et là dans son code pourrait le faire

Personnellement, j'utilise Web Workers pour stocker l'état de l'application et émettre des accessoires en cas de changement (car j'ai complètement détaché le stockage et la récupération de données à partir des composants React, et je traite React comme un moteur de rendu, pour préserver le principe de flux unidirectionnel), donc il attend juste qu'un message avec les exigences attendues (telles qu'une propriété spécifique étant vraie) à restituer.

De cette façon, même si le ou les premiers messages sont des états intermédiaires de « chargement », ils ne sont pas utilisés pour les rendus statiques côté serveur, sans avoir besoin de méthodes de composant asynchrone.

@sedubois Cela devrait être une pile de méthodes différente; ou un changement décisif. Les méthodes de rendu actuelles de React sont synchronisées, nous aurions donc besoin d'une méthode de rendu serveur différente qui renvoie une promesse.

@dantman Je ne veux pas donner d'opinion ici car ces eaux sont trop profondes pour moi (mon objectif initial était juste de souligner le hack du composant asynchroneWillMount de Vjeux...), mais hmm, je ne peux pas réagir regarder le type de objet renvoyé par les méthodes de cycle de vie et s'il s'agit d'une promesse, le gérer de manière asynchrone et sinon le gérer de manière synchronisée (comme actuellement, c'est-à-dire de manière rétrocompatible) ?

J'aime ce que @fkrauthan a proposé. Une méthode de cycle de vie load qui peut renvoyer une promesse ou undefined et une fonction asynchrone renderToStringAsync supplémentaire. Mais load doit toujours être appelé, sur le client et sur le serveur. Si nous utilisons render ou renderToString la promesse renvoyée est ignorée pour correspondre au comportement que nous avons aujourd'hui. Si nous utilisons renderToStringAsync et que load renvoie une promesse, la promesse doit être résolue avant le rendu.

Pourquoi ne pas faire comme Next.js , ajouter une fonction de cycle async getInitialProps vie React

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

Il semble que Next.js réussisse à faire le travail, sauf que son getInitialProps n'est pas lié au cycle de vie du composant React, il ne peut donc être appelé que sur le composant de niveau supérieur. Alors les idées sont là et pourraient juste être réunies ?

@sedubois Je ne pense pas que getInitialProps soit le bon endroit. Tout d'abord, les personnes qui utilisent ES6 n'ont pas/utilisent cette méthode. Deuxièmement, vous ne voulez pas travailler sur les initalProps (ce ne sont que les valeurs par défaut), vous voulez travailler sur la fusion d'initalProps + transmis dans les accessoires. Par conséquent, ma suggestion d'un nouvel événement de cycle de vie qui est expédié avant componentWillMount .

Je pense que l'idée de @Lenne231 pourrait causer des problèmes car les gens pourraient relayer d'autres événements du cycle de vie pour que cette promesse soit déjà résolue. Avoir maintenant deux comportements différents du même événement de cycle de vie rend les choses un peu délicates.

Par conséquent, il devrait y avoir sur le serveur et sur le client une méthode de rendu de synchronisation qui n'appelle pas du tout ce nouveau cycle de vie. Et une version asynchrone qui appelle ce cycle de vie et attend toujours que la promesse soit résolue avant de continuer. À mon avis, cela vous donnerait la plus grande flexibilité, car vous pouvez maintenant décider si vous avez également besoin de ce cycle de vie sur votre rendu client ou uniquement pour votre rendu serveur.

les personnes qui utilisent ES6 n'ont pas/utilisent cette méthode

@fkrauthan peut-être faites-vous référence à getInitialState ? Ici, je parlais d'un nouveau, getInitialProps (le nom utilisé dans Next.js).

Quant à votre deuxième point, oui, il serait peut-être préférable d'avoir la nouvelle méthode de cycle de vie avant componentWillMount plutôt qu'avant constructor . Je suppose que ce n'est pas ainsi dans Next car ils n'ont pas le luxe de modifier le cycle de vie de React comme mentionné précédemment. C'est pourquoi ce serait formidable d'intégrer ces idées dans React.

@sedubois Même celui-là. J'utilise par exemple la fonctionnalité es7 et je définis static props = {}; dans le corps de ma classe. Cela rend le code bien plus agréable à lire à mon avis et je suis sûr que de plus en plus de gens vont passer à cela dès que es7 sera officiellement publié.

Remarque : les propriétés de classe ne sont pas une fonctionnalité « ES7 » car ES7 a déjà été finalisé et la seule nouvelle fonctionnalité de langue est l'opérateur d'exponentiation. Vous faites référence à la proposition de propriétés de classe de l' étape 2. Cela ne fait pas encore partie du langage et peut changer ou être abandonné.

Je ne vois pas le besoin de fonctions asynchrones en réaction pour récupérer des données, au lieu de cela, je serais satisfait d'un moteur de rendu virtuel qui rendraitToString à la demande. Ainsi, vous pouvez définir des actions et l'arbre virtuel se mettra à jour en conséquence avant d'être rendu en chaîne. Je pense que ce serait idéal pour obtenir un bon SSR en réaction. Laisse au développeur comment et où récupérer les données.

J'ai déjà mentionné, mais une bonne API serait quelque chose comme ce qui suit (avec une petite mise à jour avec ce que j'ai suggéré auparavant):

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();

Cela éviterait le problème rencontré par certains clients GraphQL tels qu'Apollo qui les oblige à créer un double renderToString. Étant le premier à récupérer les dépendances de données et le second rendu avec les données renseignées. Je pense que c'est un gaspillage de traitement car renderToString est assez cher.

@bruno12mota Je vois votre point de vue, mais encore une fois, le nombre de bibliothèques qui essaient de simuler cela (et avec votre solution, il y aura toujours des milliers de bibliothèques) est pour moi un signe que cela devrait peut-être être une fonctionnalité de base plutôt que quelque chose que vous devez construire sur le dessus. Votre solution nécessiterait encore beaucoup plus d'appels de rendu (appels virtuels mais toujours rendus) pour obtenir la sortie.

@fkrauthan oui mais les rendus virtuels sont bien plus performants que d'avoir à faire deux rendus en chaîne, ce qui est le problème principal ici à mon avis (performances en SSR) c'est la partie faible de React. Il y a eu quelques expériences pour améliorer les renderToString mais il n'y a pas vraiment d'avancée sur ce sujet par l'équipe React (pas de critique ici, ils font un travail incroyable).

Je suis d'accord avec @bruno12mota , ayant également délibéré sur cette question depuis un certain temps maintenant, c'est l'approche la plus simple. Cela laisse la liberté au développeur de déterminer quand son rendu doit « vider ».

Cela rend tout beaucoup plus compliqué.

1.) Du point de vue du code (j'ai regardé dans le code et rendre le rendu asynchrone devrait être beaucoup plus facile que de créer un moteur de rendu qui utilise simplement VDom et qui peut ensuite être vidé à un moment donné)
2.) Maintenant, vous devez également reconsidérer les événements de démontage. Et cela rend le code personnalisé bien plus compliqué que nécessaire. Par exemple, le chargement de données peut entraîner le rendu d'un nouveau composant qui doit également charger des données. Maintenant, tout d'un coup, la bibliothèque a besoin de déterminer quel composant à quelle position les données déjà chargées et quel composant contient le nouveau chargement de données et d'autres choses.

Je suis avec @bruno12mota. Un VDom est sans doute une solution de plus "bas niveau", mais serait probablement plus facile à mettre en œuvre pour les développeurs React, n'aurait pas de problèmes de compatibilité descendante et pourrait permettre à la communauté d'expérimenter différentes solutions de niveau supérieur.

Par exemple, une solution de niveau supérieur pourrait être un "composant de niveau supérieur" qui encapsule les méthodes asynchrones componentWillMount , attend toutes les promesses en attente et déclenche VDom.renderToString() lorsque tout est résolu. J'utilise ce type de stratégie pour le SSR dans mon application, même si je fais malheureusement beaucoup de récupérations excessives en ce moment car il n'y a pas de VDom.

Je pense que le meilleur moyen serait de ne changer aucune des fonctions que nous avons actuellement. Ce qui signifie que getInitialState, componentWillMount ou renderToString ne se comportent pas différemment.

Au lieu de cela, nous devrions ajouter de nouvelles fonctions pour résoudre le problème discuté ici. Tout comme il y a des fonctions qui ne sont appelées que sur le client, il peut y avoir des fonctions qui ne sont appelées que sur le serveur.

Je pense que nous pourrions ajouter une fonction "renderToStringAsync" à react-dom/server. La différence avec le renderToString normal serait qu'il renvoie une promesse.

Du côté de l'implémentation, la seule différence serait que la version asynchrone, chaque fois qu'elle crée une instance de composant, appelle et attend "initialize()" dessus avant d'appeler render(). Si le composant n'a pas de méthode "initialize", il pourrait appeler render() immédiatement.

Donc, si nous avons l'exemple de structure d'application suivant :

    ComponentB
    ComponentC
        ComponentD
        ComponentE

le processus serait :

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

Donc, fondamentalement, nous avons juste besoin d'une nouvelle fonction "renderToStringAsync" qui utilise une nouvelle fonction optionnelle "initialize". Ce ne serait pas si difficile.

@VanCoding a la même idée à laquelle je pense depuis un moment. Je me demandais si initialize pouvait également être appelé automatiquement dans componentDidMount sur le client ?

Donc sur le serveur ce serait :

initialize()
render()

Et sur le client ce serait :

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

J'ai une implémentation fonctionnelle du rendu du serveur de réaction avec react-router v4 et de react-apollo avec GraphQL.

il a été inspiré par server-side-rendering-code-splitting-and-hot-reloading-with-react-router .

Fondamentalement, le rendu du serveur utilise un composant synchronisé, le rendu client utilise un composant asynchronisé afin que le chargement et l'exécution asynchrone du module Webpack javascript.

Au fait, le rendu du serveur est implémenté avec Nashorn Script Engine (JVM).

Code de rendu client
https://github.com/shendepu/react-ssr-starter-kit/blob/apollo/src/main.js#L63 -L82

Code de rendu du serveur
https://github.com/shendepu/react-ssr-starter-kit/blob/apollo/src/main.js#L128 -L155

Il y a un endroit important pour distinguer les composants synchronisés ou asynchronisés dans la route.
Composante synchronisée dans Route
https://github.com/shendepu/react-ssr-starter-kit/blob/apollo/src/routes/Counter/Route.js#L10

Composant asynchronisé dans RouteAsync
https://github.com/shendepu/react-ssr-starter-kit/blob/apollo/src/routes/Counter/RouteAsync.js#L7 -L23

REMARQUE : le nom du fichier DOIT être Route.js ou RouteAsync.js. Le code doit toujours importer RouteAsync.js. Webpack le remplacera par Route.js s'il est conçu pour le rendu du serveur.
https://github.com/shendepu/react-ssr-starter-kit/blob/apollo/build/webpack.config.js#L72 -L80

Il y aura donc deux versions de build dist et dist_ssr , dist est pour le client tandis que dist_ssr est pour le serveur qui n'a que deux morceaux : vendor et app . L'état du magasin est exporté et intégré dans <script> de index.html sous la forme __INITIAL_STATE__ . La raison d'avoir deux versions de build est que ReactDomServer.renderToString() ne prend pas encore en charge le composant asynchrone.

Le seul problème avec l'approche de build à deux versions est qu'il y a un avertissement en mode 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:

Mais puisque la seule différence entre la version client et la version serveur est Route.js et RouteAsync.js, donc l'avertissement peut être ignoré, l'avertissement ne s'affiche pas en mode production.

Pour la récupération asynchrone des données avant le rendu des composants côté serveur :

  • API Rest : Ajout de loadData statique dans l'utilisation du composant
const { matchedRoutes, params } = matchRoutesToLocation(rootRoute.routes, 
                                                        location, [], {}, rootRoute.pattern)
matchedRoutes.filter(route => route.component.loadData).map(route =>
              route.component.loadData(store, params))

pour sonder et exécuter loadData . matchedRoutes est un tableau d'itinéraires correspondants dont le parent provient de l'index 0 . loadData peut être exécuté en séquence ou en parallèle.

  • Requête GraphQL : utilisez simplement getDataFromTree de react-apollo/server pour exécuter toutes les requêtes GraphQL.

BTW : l'utilisation du moteur de script Nashorn offre une opportunité d'optimisation des performances : étant donné que le fournisseur contient du code de bibliothèque et des polyfills qui sont exécutés une fois et réutilisés, donc pour les demandes ultérieures, seul le morceau d'application est exécuté. Étant donné que les bibliothèques sont déplacées dans le segment du fournisseur, l'application ne contient que du code javascript dans src, ce qui est petit, ce qui augmente les performances par rapport à l'évaluation du segment du fournisseur à chaque fois. (tout le javascript est compilé une fois et mis en cache, exécuté pour chaque requête.)

J'ai menti en disant que le morceau d'application ne contient que du code src. il contient également babel-runtime et dupliqué core-js/library référencé par babel-runtime . babel-runtime ne peut pas être déplacé dans le morceau de fournisseur car il n'a pas d'index.js ou d'entrée principale, de sorte que webpack ne peut pas le reconnaître en tant que module. mais la taille du code est petite.

Développons davantage l'exemple de @VanCoding : https://github.com/facebook/react/issues/1739#issuecomment -261577586

Peut-être que initialize n'est pas un bon nom - le nom devrait en quelque sorte indiquer clairement qu'il est destiné à être utilisé uniquement sur le serveur, ou dans un environnement où le rendu est retardé jusqu'au retour de cette fonction - c'est-à-dire pas dans le client. Idées : getStaticProps , getServerProps , getInitialProps , getAsyncProps ...

Il serait également bon d'entendre de la part de l'équipe principale s'il existe des obstacles techniques ou autres au développement d'une telle méthode renderToStringAsync.

@nmaro Eh bien, je pense que la fonction ne devrait rien renvoyer, donc je préférerais un nom qui ne commence pas par "get", mais à part cela, je pense toujours que ce serait le meilleur moyen. Ça ne me dérange pas de savoir comment ça s'appelle à la fin.

D'ailleurs, j'ai fait une preuve de concept pour ce , dans environ 40 lignes de code. Il n'est pas destiné à une utilisation en production, mais mes tests avec divers composants ont été concluants.

Donc, si l'équipe de réaction ne veut toujours pas que cela se produise, il ne serait pas si difficile de le faire dans une bibliothèque tierce.

Alternativement, nous pourrions ajouter une option à renderToString, comme dans
renderToString(Component, {async: true})

@nmaro Cela rendrait le type de retour de la fonction dépendant des options fournies, ce que je ne considère pas comme une bonne pratique. Je pense qu'une fonction doit toujours renvoyer un résultat du même type.

@VanCoding avez-vous un exemple d'utilisation de votre code en tant que bibliothèque tierce ?

@VanCoding cool ! Mais honnêtement, je pense que ça va être difficile d'intégrer cela dans l'algorithme de rendu interne de React. Consultez https://github.com/facebook/react/blob/master/src/renderers/dom/stack/server/ReactServerRendering.js et perdez-vous dans les internes de React... OMI, nous avons vraiment besoin de l'avis de quelqu'un du coeur.

@sedubois, vous pouvez utiliser la fonction comme vous utiliseriez la fonction normale renderToString. Mais il renvoie une promesse qui se résout en une chaîne, au lieu de renvoyer une chaîne directement.

Donc, l'utiliser ressemblerait à:

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);
});

En théorie, il devrait être capable de rendre n'importe quel composant de réaction existant de la même manière que le rendu normal de renderToString. La seule différence est que les composants asynchrones sont également pris en charge.

@nmaro Vous avez raison. Ce serait utile.

Quel est le statut à ce sujet ?

@firasdib Nous attendons toujours que certains développeurs réagissent, je pense.

+1

Les problèmes d'indexation de Google exigent la SSR et tous les bagages décrits ci-dessous sont à traiter pour nous et donc ce problème est très important pour que React réussisse en tant que bibliothèque générale.

Je pense que l'ensemble du rendu asynchrone n'est pas nécessaire si JavaScript fournissait un moyen approprié d'attendre une promesse. Ensuite, nous pourrions passer un paramètre indiquant la météo ou non que nous voulons que le chargement des données soit synchrone (pour le SSR) ou async (pour le vrai navigateur Web). La logique de chargement des données pourrait lancer une promesse dans n'importe quel constructeur de composant React et attendre dans componentWillMount.

Cependant, si je comprends bien, les développeurs de React ont mis en doute l'utilité de componentWillMount et ont recommandé que le constructeur fasse tout le travail. L'une ou l'autre manière fonctionnera.

Certaines personnes ont fait valoir que faire des composants des vues pures résout le problème. Ils sont partiellement corrects. Malheureusement, cela ne fonctionne pas dans le cas le plus générique où nous ne savons pas quelles données charger jusqu'à ce que l'arbre de rendu soit terminé.

Le cœur du problème est que la fonction de rendu peut avoir une logique pour sélectionner les composants à ajouter au DOMTree. C'est ce qui rend React si puissant et si mal adapté au rendu côté serveur.

OMI, la vraie solution est de pouvoir enregistrer toutes les promesses de chargement de données émises avec React. Une fois toutes les promesses de chargement de données terminées, réagir nous rappelle avec le résultat du rendu. Cela a plusieurs implications :

  • le DOMtree sera rendu plusieurs fois. C'est comme un système vivant qui "s'installe" d'un état de chargement de données instable à un état "chargé" stable.
  • il existe un risque que le système n'atteigne pas un état stable "chargé" dans un système bogué. Un temps mort doit être mis en place pour y faire face.

Le résultat est une utilisation beaucoup plus « informatique » de la bibliothèque côté serveur avec un processus qui n'est pas rapide.

Ainsi, je réfléchis toujours à une solution de contournement qui compromettrait le cas générique en faveur de la réalisation des choses :)

En attendant, nous devons résoudre le SSR dans un environnement de chargement de données asynchrone.

  1. Les appels de chargement de données doivent être dérivés de l'URL de demande (react-router est un exemple). Collectez toutes les promesses de chargement de données dans une seule liste de promesses.
  2. Utilisez Promise.all(), qui est en soi une promesse. Lorsque cette promesse se termine, transmettez les données chargées à la fonction de rendu du serveur.

Ce modèle n'est pas lié à une architecture de conception particulière. Il semble que flux/redux puisse en bénéficier légèrement, mais je ne suis pas sûr car j'ai abandonné le modèle de redux dans mon application.

Le problème dans ce modèle est l'article n°1 ci-dessus. Une application simple connaît tous les appels de chargement de données. Dans une application complexe avec des "modules" indépendants, ce modèle nécessite une sorte de réplication de l'arborescence des composants React d'origine et même une logique de rendu.

Je ne vois pas comment SSR peut fonctionner sur un projet complexe sans répéter la logique déjà écrite dans la fonction render(). Flux pourrait être une solution, mais je n'en ai pas l'expérience pour être sûr.

La mise en œuvre de l'élément n° 1 est vague. Dans l'exemple de react-router, nous devons en tant que routeur nous renvoyer les composants de niveau supérieur qui appartiennent à cette route. Je pense que nous pouvons instancier ces composants :

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')
        }
    })
});

Ainsi, les composants de niveau supérieur ne sont plus des composants "purs". Ces composants jouent désormais un rôle de contrôleur de niveau supérieur, en raison de leur capacité à déclencher le chargement des données. Le modèle Flux ne le rend pas meilleur. Il déplace simplement la route vers l'association de la fonction de chargement de données dans un module différent.

Malheureusement, continuer avec le chargement des données de cette manière vers les enfants des contrôleurs supérieurs entraîne une duplication du graphe d'objets créé à des fins de rendu réactif. Pour garder les choses simples (si possible), nous devons mettre toutes les données de chargement au niveau du contrôleur supérieur.

C'est ce que je vois.

Si j'avais réalisé ces implications SSR, j'aurais réfléchi à deux fois à l'utilité de React pour les applications indexables sur Google. Peut-être que le flux est une solution, mais le flux rend l'application plus compliquée qu'une simple application React. J'ai été là. Au lieu d'une simple fonction de chargement de données, je dois poursuivre ma logique sur plusieurs fichiers. Au niveau théorique, ça avait l'air vraiment très bien jusqu'à ce que j'aie un projet en cours. Les nouvelles personnes ont eu du mal à démarrer. Aucun problème de ce type après avoir retiré le redux de la base de code. Je pensais que c'était la réponse à toutes les complexités de la conception de l'interface utilisateur :)

À partir de la React Conf de cette semaine, après la sortie de React Fiber (React 16), ils prévoient de travailler sur les éléments suivants (soi-disant pour React 17):

screen shot 2017-03-16 at 15 55 51

Signifie que des méthodes de cycle de vie React asynchrones pourraient arriver ?

Eh bien, n'est pas l'étoffe de fibres surtout de plus rapide / plus lisse re -rendering? Sur le serveur, nous ne rendons qu'une seule fois, donc je ne suis pas sûr que ces changements auront un effet sur le rendu côté serveur.

@VanCoding a mis à jour mon message ci-dessus qui n'était pas clair. Je voulais dire leurs plans après la

Pour les gens qui atterrissent ici comme moi, j'ai fini par créer une implémentation personnalisée à l'aide de React qui pourrait aider.

https://github.com/siddharthkp/reaqt

la clé est asyncComponentWillMount qui s'exécute uniquement sur le serveur. il n'est disponible que pour les composants du point d'entrée, pas pour tous les composants pour éviter les problèmes mentionnés ici.

@siddharthkp Je ne vois pas vraiment ce que votre composant essaie de résoudre ? S'il ne prend en charge qu'asyncComponentWillMount pour le composant React d'application de niveau supérieur, pourquoi devrais-je utiliser votre bibliothèque ? Je pourrais simplement résoudre cette promesse à l'extérieur, puis appeler le rendu ? Ou j'ai raté quelque chose ?

@fkrauthan

Je pourrais simplement résoudre cette promesse à l'extérieur, puis appeler le rendu ?

Tu as raison, c'est exactement ce qu'il fait.

L'intérêt est que vous obtenez async ssr (+ code splitting + hmr) avec une seule commande. J'ai vu beaucoup de gens ne pas implémenter ssr parce que ce n'est pas très facile, et c'était la motivation.

il prend uniquement en charge asyncComponentWillMount pour le composant React d'application de niveau supérieur

  1. N'a pas besoin d'un seul composant d'application. Vous pouvez récupérer des données pour chaque page/écran/point d'entrée. (en faisant la promotion de plusieurs points d'entrée ici )

  2. Le modèle est intentionnel, si chaque composant de l'arbre veut ses propres données, nous pourrions finir par encourager un mauvais modèle pour les performances. La récupération des données au niveau du composant supérieur et leur transmission aux enfants en tant qu'accessoires les gardent sous contrôle.

@siddharthkp comment votre référentiel s'améliore-t-il par rapport à Next.js (qui a également été présenté à React Conf BTW) ?

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

Quoi qu'il en soit, la discussion sur les bibliothèques tierces est hors sujet à mon humble avis, ce qui nous intéresse, c'est le support au sein de React lui-même.

la discussion sur les bibliothèques tierces est hors sujet à mon humble avis, ce qui nous intéresse, c'est le support au sein de React lui-même.

Je suis complètement d'accord. Peut discuter de questions spécifiques dans des problèmes réels

Un cas d'utilisation que j'ai pour cela consiste à utiliser des icônes vectorielles réactives et natives. Je veux que la prop d'un composant soit déterminée par les données reçues d'une promesse

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

Si pris en charge, je pense que cela rendrait beaucoup de code complexe plus facile à comprendre et éventuellement ouvrirait de nouveaux modèles asynchrones.

nous avons le même problème, je veux faire le ssr, non seulement rechercher les props de prédiction mais aussi le contenu récupéré par action dans componentWillMount, comment puis-je attendre que les props soient préparés, puis rendre les props en chaîne.

Si quelqu'un trouve cela utile, j'ai écrit une bibliothèque appelée react-frontload qui vous permet de lier une fonction de retour de promesse à un composant à exécuter lors du rendu du composant, à la fois sur le serveur et sur le client.

La fonction s'exécute "de manière synchrone" sur le rendu du serveur (enfin pas vraiment ;-) mais le rendu final du composant est bloqué jusqu'à ce qu'il soit résolu), et de manière asynchrone comme d'habitude sur le rendu client, et ne s'exécute pas à nouveau sur le client si la page était juste rendu par le serveur. Il existe également d'autres options que vous pouvez spécifier pour un contrôle plus fin de son exécution, par composant.

C'est vraiment utile pour le chargement de données - à peu près le cas d'utilisation pour lequel je l'ai écrit. Essaye le! ??

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

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

Pensez à tout le rendu conditionnel que vous n'auriez pas à faire avec l'approche ci-dessus parce que vous savez que vous avez vos données

Se joindre à celui-ci. Je pense que c'est toujours une fonctionnalité essentielle qui manque à React. Convenez également que même si des bibliothèques tierces peuvent couvrir ce sujet, cela devrait venir directement de l'intérieur.

J'ai proposé une implémentation https://github.com/timurtu/react-render-async

Très belle interface de composant asynchrone, @timurtu.

... Hé tout le monde, je voulais juste intervenir ici car j'ai plusieurs choses qui se rapportent très directement à ce fil, et je suis ce fil depuis longtemps :

  • D'une part, compte tenu de la SSR, je pense que la récupération de données au niveau de la route est la plus logique - voici pourquoi :
    Récupération de données Redux-First Router : résolution du cas d'utilisation à 80 % du middleware asynchrone
  • le rendu du serveur avec Redux est maintenant stupide et simple, voici un guide étape par étape avec Redux-First Router sur la façon de le faire comme vous ne l'avez jamais vu auparavant : Server-Render comme un Pro /w Redux-First Router en 10 étapes
  • enfin, pour faire correctement le fractionnement de code, vous devez également faire les deux (c'est-à-dire les deux SSR + Splitting) . Il s'avère que faire les deux simultanément a été un problème majeur , atteint avec succès par quelques-uns, et jusqu'à présent, il n'y avait jamais eu de package général pour aider la communauté. React Universal Component + babel-plugin-universal-import + webpack-flush-chunks (et extract-css-chunks-webpack-plugin ) sont une famille de packages qui résolvent vraiment ce problème. Et pour la première fois. Fondamentalement, il vous permet de rendre les composants de manière synchrone sur le serveur, mais plus important encore, il transporte les morceaux exacts rendus au client pour un rendu synchrone initial sur le client également. Vous obtenez même des feuilles de style CSS chunk via le plugin webpack. C'est différent de ce que @timurtu a partagé et de nombreux composants de ce type, qui nécessitent le chargement de main.js , l'analyse, l'évaluation, le rendu et enfin quelques secondes plus tard, la demande des importations dynamiques. Ce sont des solutions pour résoudre le flash de contenu non stylisé (FOUC), mais pas pour intégrer dans votre page les morceaux exacts rendus sur le serveur. Universal - le nom de ma famille de packages - vous permet de le faire avec un temps beaucoup plus rapide React Universal Component 2.0 & babel-plugin-universal-import

Je sais que je n'ai pas abordé les avantages/inconvénients au niveau de la route par rapport au composants , mais le premier lien se concentre principalement sur cela. Fondamentalement, cela se résume au niveau de composant qui est mauvais pour le SSR si vous avez des composants imbriqués dont les données doivent être récupérées en séquence (plutôt qu'en parallèle). Et si vous n'avez correctement qu'une seule récupération à faire par route, eh bien, vous feriez mieux de formaliser le contrat en attachant vos dépendances de données à une entité de route, au lieu de le faire en componentDidMount . Beaucoup d'autres ci-dessus sont arrivés à cette même conclusion. Redux-First Router formalise cela très bien dans le sens du genre de "contrats" qui rendent Redux si agréable à travailler.

Redux-First Router renouvelle de nombreux anciens paradigmes, mais s'intègre étonnamment parfaitement comme pièce manquante de l'écosystème Redux. Si vous avez déjà voulu un routeur natif du flux de travail de Redux, essayez Redux-First Router. C'est relativement nouveau, et je suis un nouveau venu dans l'open source, mais c'est quelque chose sur lequel j'ai travaillé pendant environ un an et ça a beaucoup attiré au cours des 2 mois de sa sortie. Les gens adorent ça. J'espère vraiment que vous le vérifierez et que vous tenterez le coup :).

Voici également son article de lancement qui explique en quoi il s'agit d'une bien meilleure solution pour Redux que React Router, et comment il obtient la bonne vision d'une manière que l'excellent Redux-Little Router a malheureusement manqué :
Avant-première : Redux-First Router - Un pas au-delà de Redux-Little-Router

La principale différence entre RLR et RFR est que RFR envoie un type d'action différent par route (c'est-à-dire un changement d'URL), au lieu de toujours LOCATION_CHANGED . Cela vous permet de basculer les modifications d'URL en tant que types uniques dans vos réducteurs, comme toute autre action. Cela peut sembler petit, mais cela fait une énorme différence. Pour cette raison, il ne nécessite pas <Route /> composants prises en charge par thunks à React Native, React Navigation, la division de code, la préchargement, etc.

J'adorerais entendre les pensées des gens.

@faceyspacey merci, je pense que cela résout le problème que j'avais, c'est-à-dire que lorsque vous souhaitez définir des accessoires basés sur des données asynchrones, par exemple, react-native-vector-icons peut renvoyer une icône en guise de promesse. C'est certainement une façon différente de voir les choses, mais cela réduit componentWillMount ou redux X_GET_START , X_GET_END , et X_GET_ERROR action passe-partout qui lient les composants asynchrones à l'état, lorsque vous ne voulez vraiment faire cette demande qu'une seule fois pour rendre le composant. Si vous faites des choses comme interroger des données, alors être avec état et utiliser le redux est probablement plus logique

@faceyspacey Je vois ce que vous dites au sujet du niveau de route, mais ne serait-il pas préférable d'avoir des composants plus petits qui ne reposent que sur la récupération des données dont ils ont besoin tout en rendant tout le reste autour d'eux de manière synchrone?

Mieux? Où? Sur React Native ? Dans les grandes organisations comme Facebook sur leurs applications React Native ?

Peut-être sur React Native. Quand il y a SSR à mon avis, il n'y a pas de débat. Ce que vous voulez faire n'est tout simplement pas possible sans plusieurs extractions par requête, à moins, encore une fois, de le limiter à un seul composant pouvant effectuer l'extraction. À ce stade, il vaut mieux formaliser le contrat avec les entités de routage.

Quant à React Native et aux SPA, bien sûr. Mais tester des composants qui intègrent des dépôts de données est une tâche ardue. Apollo a quelques trucs ici, mais la dernière fois que j'ai vérifié, ils avaient une longue liste de choses à faire pour obtenir des tests de composants appariés vraiment corrects, c'est-à-dire sans couture. Je préfère une configuration où vous avez un magasin facile à configurer et à simuler. Vous pouvez réutiliser la façon dont vous faites cela dans tous vos tests. Ensuite, à partir de là, le test des composants est un simple rendu synchrone avec prise d'instantané. Une fois que vous avez ajouté des données dans les composants, le test de React devient moins intuitif. Mais les gens le font et l'automatisent. Il y a moins de norme et c'est maintenant plus proche du code ad-hoc personnalisé. Il est plus productif d'avoir un magasin qui peut être rempli de manière asynchrone de manière très évidente, plutôt que d'avoir des stratégies potentiellement différentes pour chaque composant asynchrone qui doit être rempli.

Je ne suis pas contre le niveau des composants s'il n'y a pas de SSR et que votre stratégie de test est transparente et à l'écart. Si vous êtes une méga entreprise, cela a du sens, car chaque développeur n'a pas besoin de toucher au niveau supérieur de la route et de le casser potentiellement pour les autres. C'est pourquoi FB a été le pionnier de cette route avec Relay et GraphQL. Le fait est que seulement 100 entreprises dans le monde sont vraiment dans ce bateau. Je considère donc la colocation comme plus de battage médiatique que de réalité pour la plupart des personnes/applications/entreprises. Une fois que vous maîtrisez le niveau de route et que vous disposez d'un package qui le fait très bien comme Redux-First Router , vous disposez d'une approche beaucoup moins enchevêtrée et plus facile à développer pour les équipes dont les membres sont autorisés à toucher le niveau de la route . Consultez simplement la démo sur la page d'accueil de codesandbox :

https://www.codesandbox.io

En gros, une fois que vous voyez comment c'est fait, j'aimerais avoir votre avis. Gardez à l'esprit qu'il s'agit d'un SPA. Vous devrez donc revoir le référentiel de démonstration ou le modèle standard pour un exemple SSR. Mais c'est un bon début.

Je pense que le moyen le plus simple sera de prendre en charge la méthode de rendu asynchrone (le rendu renvoie une promesse). Cela permettra au composant racine d'attendre le rendu des composants enfants et, bien sûr, le résultat du rendu sera une promesse. J'ai implémenté quelque chose comme ça dans preact ici https://github.com/3axap4eHko/preact-async-example

@faceyspacey Merci pour la réponse détaillée ci-dessus. J'aime beaucoup l'idée de Redux-First Router, cela résout définitivement tous mes problèmes et rend les choses beaucoup plus transparentes et propres que ce que nous avions auparavant.

Grand merci!

@raRaRa content que ça te niveau des routes jusqu'à présent. React React v3 avait des rappels, et la v4 a un excellent modèle (en particulier avec le package react-router-config ), mais ce n'est pas évident et pas de première classe dans la v4. C'est une réflexion après coup. Plus important encore, il ne vit pas dans Redux. Ce n'est donc que maintenant pour la première fois que les utilisateurs de redux ont une abstraction complète au "niveau route".

Je pense que maintenant avec RFR, nous allons revoir l'importance du "niveau de route" par rapport au "niveau de composant". Des choses comme Relay et Apollo ont créé beaucoup de battage autour de l'association des dépendances de données aux composants, mais si vous n'utilisez pas ces 2, vous allez probablement vous retrouver dans un monde blessé à un moment donné en faisant des choses dans les gestionnaires de cycle de vie .

Cela dit, RFR finira par avoir une intégration au niveau de la route avec Apollo. Il n'y a que très peu d'avantages à faire des choses au niveau des composants, moins les rares exceptions susmentionnées. Au moins 80 % du temps (et peut-être 100 % dans votre application), vous pouvez déterminer toutes les données dont vous avez besoin en fonction de l'itinéraire. Si ce n'est pas possible, c'est probablement parce que vous dérivez des données dans votre "couche de vue" qui pourraient très bien être repensées pour être toutes interprétées par l'URL. Ainsi, même avec des outils comme Apollo, vous commencez à vous engager dans des anti-modèles problématiques qui finiront probablement par vous causer de la douleur en laissant « état » vivre nulle part ailleurs que la couche de vue. C'est-à-dire l'état nécessaire pour effectuer ces récupérations de données. Vous devez souvent affiner les paramètres exacts utilisés pour récupérer vos données. Donc, vous prenez l'état et les accessoires et vous transformez les 2 pour obtenir les paramètres juste avant de récupérer les données. Ce sont des choses qui vivent maintenant dans la couche de vue et qui sont soumises à ses mécanismes de rendu (c'est-à-dire lorsque les gestionnaires de cycle de vie sont appelés). Chaque application à un moment ou à un autre se retrouve avec les données non récupérées quand elles devraient l'être ou récupérées en réponse à des accessoires non liés reçus/modifiés. Et dans toutes les applications, vous devez écrire du code supplémentaire pour vous protéger contre les accessoires non liés qui ont changé. C'est-à-dire des accessoires qui n'affectent pas les paramètres utilisés pour récupérer les données.

En ce qui concerne React Native + Apollo, le problème est moindre car vous n'avez pas de SSR et donc un potentiel de récupération de données séquentielle sur le serveur. Cependant, avez-vous vraiment besoin d'une récupération de données au niveau des composants lorsque les écrans sont si petits. Vous savez généralement ce dont chaque "scène" a besoin en termes de données. Ce n'est pas comme un tableau de bord de panneau Web de bureau avec un million de graphiques, plusieurs tableaux, etc. Ce cas d'utilisation est peut-être le principal endroit où le niveau des composants commence à avoir un sens. Mais même là, vous pouvez spécifier toutes vos exigences pour tous ces widgets en un seul endroit et les récupérer via Apollo. Je n'y ai pas encore assez réfléchi, mais l'idée de base est d'attacher vos exigences de données GraphQL à une route, puis dans vos composants de se connecter aux données comme n'importe quelle autre donnée Redux. Plus précisément, l'idée est d'éliminer à la fois le connect react-redux et l'équivalent d'Apollo. J'en veux juste un, et nous voulons qu'il soit plus plat. L'utilisation d'Apollo nécessite de déstructurer des données plus profondément imbriquées pour accéder à ce que vous voulez vraiment dans vos composants. La vision est un moyen qui ressemble à Redux ordinaire, plus les spécifications graphql au niveau de la route.

Il est un peu prématuré d'en parler, car je l'ai à peine étudié, mais si le niveau de route est la bonne approche pour votre application, Apollo + GraphQL ne devrait pas faire exception.

Enfin, les tests sont tellement plus faciles si toutes vos dépendances de données et votre travail asynchrone sont séparés des composants. La séparation des préoccupations est un accélérateur de productivité majeur lorsque vous pouvez tester vos composants sans avoir à vous soucier de la récupération des données. Bien sûr, dans vos tests, vous devez remplir votre magasin en effectuant ces récupérations asynchrones à l'avance, mais avec eux séparés de vos composants, vous pouvez facilement formaliser un tel travail sous certaines fonctions d'assistance setup tous vos tests utilisent pour pré- remplissez le magasin avant de tester le rendu des composants :)

@faceyspacey Je suis tout à fait d'accord. Mais je dois admettre que je n'ai pas essayé Apollo ni GraphQL, je vais certainement les vérifier et voir comment ils correspondent à mes cas d'utilisation.

Je suis très habitué à écrire des applications Web à l'aide du modèle MVC, où je récupère essentiellement toutes les données et dépendances sur le contrôleur. Ensuite, j'injecte les données dans les vues ou la logique métier. Comme vous l'avez dit, cela rend les tests beaucoup plus faciles car la vue n'est pas couplée à la récupération des données.

J'ai vu des applications MVC où la partie View récupère des données et fait de la logique métier, et c'est terrible. Les deux, je n'ai aucune idée des données qui seront récupérées car elles sont cachées dans une vue/un composant et les tests deviennent plus difficiles/impossibles.

Pour moi, Redux-First Router ressemble beaucoup à la partie contrôleur de MVC, plus les routes. Ce qui a tellement de sens :)

Merci.

En fin de compte, notre nouvelle pile est la suivante :

M : Redux
V : Réagir
C : Redux-First Router (bientôt renommé en "Rudy" )

Lorsque React est sorti (et en particulier Redux), nous semblions tous vouloir jeter l'ancienne sagesse de MVC, comme si d'une manière ou d'une autre cela signifiait que nous pouvions nous débarrasser complètement des difficultés liées au développement d'applications précédentes. Mais je pense qu'en fin de compte, ce n'est rien de plus que de ne pas encore avoir les outils construits pour le prendre en charge. En fin de compte, nous bénéficions toujours grandement de MVC, même dans les applications réactives client-first .

Cela peut fonctionner un peu différemment dans la nouvelle pile client d'abord par rapport aux MVC traditionnels côté serveur, mais fondamentalement, c'est la même séparation des préoccupations.

En ce qui concerne la différence d'interaction/de comportement entre les 3, c'est essentiellement que dans le passé, le contrôleur obtenait des modèles dans une phase initiale, puis rendait les vues avec eux ; et maintenant c'est le contrôleur (RFR) qui rend immédiatement une vue (choisi via un "modèle d'interface utilisateur", c'est-à-dire l'état Redux), puis qui rend la vue une deuxième fois une modèles de domaine (également stockés dans l'état Redux).

Salut les gars, regardez ce lien. voici un exemple de rendu côté serveur de données https://github.com/bananaoomarang/isomorphic-redux

@ harut55555 idk c'est peut-être juste moi mais cela semble être beaucoup de code pour faire une demande d'obtention et une demande de publication

Tout changement? Libéré 16 réagissent et la plupart des solutions ne fonctionnent pas

Je pense que l'approche actuelle consiste à utiliser le redux avec des outils comme le redux observable ou le redux thunk

Mon cas d'utilisation

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

Le problème est que je ne sais pas avant de rendre l'App quel type de composants j'aurai

Pourquoi ne pas télécharger des données/contenu au lieu d'un composant simplement curieux ?

Lorsque? J'ai des pages dynamiques et le composant peut ou non être

On dirait que vous voulez un rendu conditionnel https://reactjs.org/docs/conditional-rendering.html

Je pense que c'est un problème de routage, réagir ne devrait rendre que la page, pas gérer et demander des données de rendu

L'utilisation de @graphql d'Apollo rend le chargement des données très simple, et l'API basée sur les composants React-Router v4 permet un routage composable simple. Les deux sont orthogonaux à l'état de l'application dans Redux.

Afin de faire un rendu en streaming en une seule passe qui attend les récupérations de données, il devrait être possible pour render() de renvoyer une promesse (sur le client que vous faites comme maintenant, uniquement sur le serveur que vous renvoyez une promesse).

Ensuite, @graphql peut simplement renvoyer une promesse pour le rendu() après le chargement des données ; tout le reste est tout simplement React.

Il y a quelques mois, j'ai donné une conférence à JSConf Iceland qui décrit les prochaines fonctionnalités de rendu asynchrone dans React (voir la deuxième partie) : https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react -16.html. Il s'agit de la récupération de données côté client.

Maintenant, @acdlite a https://www.youtube.com/watch?v= z-6JC0_cOns

J'espère que vous apprécierez de regarder ces discussions ! Je pense que dans un an environ, nous pourrons peut-être clore ce problème et avoir une stratégie officielle pour cela.

Je me souviens de la bague. Nous nous marions en planifiant un avenir ensemble. S'il vous plait, terminez ça maintenant et venez me voir. Le temps travaillé aime toujours

Il est possible d'utiliser ReactDOMServer.renderToStaticMarkup pour parcourir l'arbre, placer un signet là où il atteint les dépendances asynchrones (généralement en chargeant des données à partir d'une API) et continuer à parcourir l'arbre au fur et à mesure que ces dépendances sont résolues, jusqu'à ce que l'arbre entier soit résolu, puis effectuez un dernier ReactDOMServer.renderToString pour effectuer un rendu en HTML, qui sera synchrone car toutes les dépendances sont maintenant résolues.

J'ai adopté cette approche dans react-baconjs : render-to-html.js . Il a été inspiré par un commentaire de @gaearon fait dans un autre numéro recommandant une telle approche.

@steve-taylor ouais, ça marche. C'est également la solution de contournement que j'utilise dans react-frontload qui est une solution plus générale à cela qui fonctionnera dans n'importe quelle application React.

Comme vous le dites, il s'agit essentiellement de simuler un rendu asynchrone en exécutant deux fois la logique de rendu synchrone et en attendant que toutes les promesses de chargement de données que vous avez atteintes sur le premier rendu soient résolues avant d'exécuter le second.

Cela fonctionne assez bien malgré un gaspillage évident (la sortie du premier rendu est plus que de simples promesses, c'est aussi le HTML réel qui est simplement jeté). Sera incroyable lorsque le véritable rendu du serveur asynchrone sera intégré à React.

@davnicwil beau travail ! J'ai pensé à généraliser la solution SSR asynchrone spécifique. React-frontload gère-t-il une profondeur asynchrone illimitée ? Demander parce que vous avez dit « deux fois ».

@steve-taylor bravo ! Oui, si par profondeur vous entendez la profondeur du composant dans l'arbre, c'est illimité. Il vous permet de déclarer une fonction de chargement de données sur le composant lui-même (avec un composant d'ordre supérieur), puis lorsque cela est rencontré lors du premier rendu, il est exécuté et la promesse est collectée.

Ce qui ne fonctionnera pas, c'est lorsqu'il y a d'autres composants enfants qui chargent également des données de manière asynchrone qui seraient ensuite rendues dynamiquement en fonction du résultat, si cela a du sens. Cela signifie simplement que l'application doit être structurée de manière à ce qu'il n'y ait pas ce genre d'imbrication, ce que j'ai trouvé dans la pratique n'est pas vraiment une limitation massive. En fait, à bien des égards, c'est mieux du point de vue UX, car bien sûr, la logique asynchrone imbriquée signifie des demandes en série et des temps d'attente plus longs, tandis que l'aplatissement signifie des demandes parallèles.

Cela dit, le problème d'imbrication (en fait peut-être tout ce problème de rendu de serveur asynchrone) peut être résolu avec l'arrivée de Suspense dans React dans un avenir proche. ??

Pour info, nous avons commencé à travailler là-dessus.

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

Génial @gaearon !

@davnicwil react-baconjs prend en charge une profondeur illimitée.

@gaearon Je me demande si cela rendrait possible la prise en charge des observables dans create-subscription, afin que je puisse convertir un JSX de tir observable en un composant de réaction?

J'aimerais avoir cette fonctionnalité pour deux raisons. Tout d'abord, dans mon application, l'état est stocké dans un agent Web. Ainsi, bien que la récupération des bits de cet état requis par le composant soit une opération asynchrone, cela prend environ 5 ms et cela n'a aucun sens que React rende quoi que ce soit en attendant les données. Deuxièmement, il n'existe actuellement aucun moyen universel de convertir un observable en un composant : si votre observable émet la première valeur de manière synchrone, alors ce que vous faites est de vous abonner pour obtenir cette valeur, puis de vous désabonner, puis de vous abonner à nouveau pour écouter les modifications (c'est le Replay exemple de sujet dans les documents de création observables). Et s'il émet la première valeur de manière asynchrone, vous rendez initialement nul et écoutez les modifications.

@steve-taylor react-frontload prend désormais également en charge une profondeur illimitée d'imbrication de composants, avec la version 1.0.7 .

J'espère que cette bibliothèque est utile pour certaines personnes qui atterrissent sur ce fil de discussion - si vous recherchez une solution de chargement de données asynchrone qui fonctionne sur le rendu client / serveur, avec un effort d'intégration très minime, vous devriez la vérifier.

Nous avions déjà rencontré ce problème car notre application était construite avec des crochets React, puis nous avons créé un package react-use-api pour le résoudre, qui est un crochet personnalisé qui récupère les données de l'API et prend en charge le SSR. J'espère que le paquet peut aider les personnes dans le besoin.

Cependant, nous attendons toujours avec impatience une solution officielle, tout comme le dit react-frontload , il n'y a aucun moyen intégré d'attendre que le chargement asynchrone des données se produise une fois que le rendu commence actuellement.

Cette page vous a été utile?
0 / 5 - 0 notes