React: Unterstützt asynchrones Server-Rendering (vor dem Rendern auf Daten warten)

Erstellt am 24. Juni 2014  ·  139Kommentare  ·  Quelle: facebook/react

Es würde den Prozess des Erstellens von etwas Isomorphem ernsthaft erleichtern, wenn ComponentWillMount ein Promise zurückgeben könnte und diese Reaktion das Rendern verzögern würde, bis dieses Promise gelöst ist. Ich habe Versuche gesehen, so etwas in Reak-Router und Router zu tun, aber es wäre für mich sinnvoller, diese Verantwortung jeder Komponente anstelle eines Router-Moduls zu übertragen.

Component API Server Rendering Backlog Feature Request

Hilfreichster Kommentar

Vor einigen Monaten habe ich auf der JSConf Island einen Vortrag gehalten, der die kommenden asynchronen Rendering-Funktionen in React beschreibt (siehe den zweiten Teil): https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react -16.html. Hier geht es um den clientseitigen Datenabruf.

Jetzt hielt @acdlite einen Vortrag darüber, wie die gleichen Konzepte angewendet werden können, um das asynchrone Warten auf Daten auf dem Server in React-Komponenten zu ermöglichen und das Markup nach seiner https://www.youtube.com/watch?v= z-6JC0_cOns

Viel Spaß beim Anschauen dieser Vorträge! Ich denke, dass wir in etwa einem Jahr in der Lage sein werden, dieses Thema abzuschließen und eine offizielle Strategie dafür zu haben.

Alle 139 Kommentare

Der Hauptgrund (glaube ich), dass dies noch nicht existiert, ist, dass Sie auf der Client-Seite im Grunde immer eine Art Ladeindikator anzeigen möchten, anstatt das Rendering zu verschieben. (Es würde den Code auch deutlich komplexer machen, aber damit können wir wahrscheinlich umgehen.)

Es gibt 2 Fälle, die ich ohne das schwer zu lösen finde:

  • Wenn Sie auf der Serverseite Daten vor dem Rendern abrufen möchten, können Sie den Datenabruf nicht an Komponenten delegieren, da Sie nicht über die Informationen verfügen, welche Komponente gerendert wird
  • Wenn Sie auf der Clientseite Ihre Anwendung zum ersten Mal mounten, nachdem Sie vorgerenderten HTML-Code erhalten haben, möchten Sie möglicherweise die asynchrone Methode verwenden, um diese Daten abzurufen, selbst wenn Sie eine Art Cache von Daten haben, die auf dem Server abgerufen wurden, und dies würde eine Reaktion verhindern von der Wiederverwendung des HTML.

React-Async löst diese Probleme mit Glasfasern und Cache. Das macht den Trick, aber aus meiner Sicht sind das nur _hackishe_ Lösungen, um ein Problem zu lösen, das nur im Kern gelöst werden kann.

Lassen Sie mich zu diesem Thema uninformiert, componentWillMount asynchron ist und Sie nicht sofort etwas zurückgeben. Was soll React rendern, bis nichts da ist? Wenn ja, warum nicht einfach nichts beim Rendern zurückgeben, wenn noch keine Daten vorhanden sind? (Ja, ich bekomme serverseitig) Was sollte außerdem passieren, wenn sich die Requisiten ändern, bevor componentWillMount ausgelöst wird?

Persönlich scheint es falsch zu sein, asynchrone Anforderungen während componentWillMount zu versenden, es sei denn, die Komponente ist wirklich eine isolierte Blackbox und implementiert auch einen Ladeindikator. Soweit ich das verstanden habe, sollten React-Komponenten nicht mit konventionelleren OOP-Instanzen verwechselt werden. Im besten Fall ist eine React-Komponente ein Werkzeug zur Visualisierung der Daten in Requisiten, wenn sie interaktiv ist, dann möglicherweise auch Zustand. Es ist eine Ansicht, keine Ansicht und kein Modell.

In meinen Ohren klingt das nach dem Problem, React-Komponenten sollten nicht diejenigen sein, die die asynchronen Anfragen versenden, Sie holen alle Daten ab und wenn diese Daten bereit sind, rufen Sie erst dann React.renderComponent . Dieselbe Lösung clientseitig und serverseitig. Sie haben auch die Möglichkeit, mit einem Ergebnis Ihrer Wahl abzubrechen, wenn eine asynchrone Anforderung fehlschlägt.

Sie können mich gerne entlassen, wenn ich etwas falsch verstanden habe, aber es scheint, dass Sie React-Komponenten als Ansicht und Modell behandeln, obwohl sie (wie es scheint) nur als Ansicht gemeint sind.

Färbe mich zu diesem Thema uninformiert, asynchron ist und Sie nicht sofort etwas zurückgeben. Was soll React rendern, bis nichts da ist? Wenn ja, warum nicht einfach nichts beim Rendern zurückgeben, wenn noch keine Daten vorhanden sind? (Ja, ich bekomme serverseitig) Was sollte außerdem passieren, wenn sich die Requisiten ändern, bevor ComponentWillMount ausgelöst wird?

Ich muss zugeben, dass ich nicht an alle Fälle gedacht habe^^.
Diese Funktion wäre nur nützlich, wenn wir die Komponente der obersten Ebene zum ersten Mal mounten, und auf dem Server ist es wahr, dass Sie ansonsten in den meisten Besetzungen eine Ladeanzeige anzeigen möchten.

Persönlich scheint es falsch zu sein, asynchrone Anforderungen während der ComponentWillMount zu versenden, es sei denn, die Komponente ist wirklich eine isolierte Blackbox und implementiert auch einen Ladeindikator. Soweit ich das verstanden habe, sollten React-Komponenten nicht mit konventionelleren OOP-Instanzen verwechselt werden. Im besten Fall ist eine React-Komponente ein Werkzeug zur Visualisierung der Daten in Requisiten, wenn sie interaktiv ist, dann möglicherweise auch Zustand. Es ist eine Ansicht, keine Ansicht und kein Modell.

In gewisser Weise möchten Sie, dass eine Komponente der obersten Ebene in der Lage ist, Daten abzurufen, wie dies im Flux-Beispiel der
In diesem Beispiel sind die Dinge ziemlich einfach, da das Abrufen der Todo-Liste ein synchroner Vorgang ist, wenn dies nicht der Fall wäre, und im Falle des Vorrenderns auf dem Server würden wir ein erstes Mal ohne Daten rendern (und das vorgerenderte verlieren Markup vom Server).

Im Fall einer einfachen Anwendung mit einem Datensatz, der von einer Ansichtshierarchie angezeigt wird, gibt es immer noch keine so großen Probleme, Sie können Daten vorab laden und trotzdem die synchrone Eigenschaft Ihres Shops beibehalten.
Im Falle einer Anwendung, die aus mehreren Modulen besteht, die Sie in Ihrer Anwendung wiederverwenden, möchte ich diese Module als separate Anwendungen behandeln, die sich in verschiedenen Stores "abonnieren" können (die für das Abrufen von Daten verantwortlich wären).

Vielleicht, dass ich die Dinge falsch verstehe, aber einige Diskussionen / Beispiele im Internet lassen mich denken, dass irgendwo etwas fehlt:

  • In einigen Beispielen scheint mir, dass @petehunt versucht hat, etwas Ähnliches zu erreichen.
  • response-nested-router fördert einen ähnlichen Mechanismus in willTransitionTo , und einige Diskussionen geben mir das Gefühl, dass niemand eine richtige Lösung gefunden hat.
  • RRouter bietet auch einen Mechanismus zum Vorabrufen von Daten, während die Komponente gerendert/gemountet wird.
  • und schließlich reagieren-async, wie ich bereits sagte.

@fdecampredon Um es klar zu willTransitionTo im React-Nested-Router ist _nicht_ zum Laden von Daten, insbesondere weil das Zurückgeben eines Versprechens von dieser Methode tatsächlich das Rendern einer neuen Benutzeroberfläche blockiert, was Sie nicht tun möchten es sei denn, Sie müssen unbedingt.

@fdecampredon Jeder versucht immer noch, die Dinge herauszufinden, daher würde es mich nicht überraschen, wenn niemand eine endgültige Antwort hat. Aber ich vermute, die Entwickler von Facebook müssen selbst ein paar Mal darauf gestoßen sein.

Gibt es hierzu Neuigkeiten? Ich habe gerade angefangen, React zu erkunden und stoße sofort darauf. Viele Leute empfehlen React als Lösung zum Erstellen isomorpher Apps, aber solange dies nicht gelöst ist, denke ich, dass es die Aufgabe einfach nicht erledigen kann.

In meinen Ohren klingt das nach dem Problem, React-Komponenten sollten nicht diejenigen sein, die die asynchronen Anfragen versenden, Sie holen alle Daten ab und wenn diese Daten bereit sind, rufen Sie erst dann React.renderComponent auf. Dieselbe Lösung clientseitig und serverseitig. Sie haben auch die Möglichkeit, mit einem Ergebnis Ihrer Wahl abzubrechen, wenn eine asynchrone Anforderung fehlschlägt.

Sie können mich gerne entlassen, wenn ich etwas falsch verstanden habe, aber es scheint, dass Sie React-Komponenten als Ansicht und Modell behandeln, obwohl sie (wie es scheint) nur als Ansicht gemeint sind.

Wenn dies zutrifft, ist React nichts anderes als eine etwas andere Vorlagenlösung / Ansichtsebene. Und das wäre schade, denn es gibt so ein Potenzial. Ich verstehe @fdecampredon wirklich, wenn er diese komplexen Anwendungen erwähnt, die aus mehreren Modulen bestehen. React wäre dafür perfekt.

Ich glaube nicht, dass dieser Ansatz bedeuten würde, eine Komponente als Ansicht und Modell zu behandeln. Betrachtet man die Flux-Architektur, denkt man an React-Komponenten wie an _Controller-Views_, die nicht nur Daten (aus Stores) anzeigen, sondern auch Aktionen basierend auf Benutzerinteraktionen aufrufen. Und die Aktionen aktualisieren dann die Stores (=Modell). Für mich klingt es wie eine offensichtliche MVC-Architektur.

Das Problem liegt bei der ersten Aktion, die den Speicher auffüllt. Auf der Clientseite ist es ziemlich einfach, wo die Aktion wie empfohlen componentDidMount() Methode

Im Moment scheint mir der einzige Weg React-async/Fibers + Flux zu sein. Das Schöne an Flux ist, dass wir keinen künstlichen Cache benötigen, um Komponentenzustände vom Server zum Client zu transportieren (wie es im ursprünglichen React-Async-Beispiel gemacht wird), wir können die Stores einfach initialisieren und dann an die senden Client mit dem HTML-Markup (siehe dieses Beispiel ).

Aber diese Lösung ist in der Tat _hackish_.

Ich glaube nicht, dass es unbedingt ComponentWillMount sein muss, das asynchron ist; Ich bin mir nicht einmal sicher, ob es ein Lebenszyklusereignis sein muss. Das eigentliche Problem besteht darin, dass es serverseitig keine Möglichkeit gibt, den Komponentenbaum zu analysieren, bis alles in einen String gerendert wurde.

Meine ideale Lösung, um dies zu lösen, wäre, das "Rendern" zuzulassen, das nur den Komponentenbaum erstellt, dann könnten wir den Baum durchsuchen, um Komponenten zu finden, die zusätzliche Daten benötigen, uns erlauben, mehr Daten asynchron zu laden und "neu zu rendern". " dieser Komponenten-Unterstruktur, und sobald wir zum Leeren des Markups bereit sind, erlauben Sie uns, diesen Baum in eine Zeichenfolge zu konvertieren.

Dies repliziert, was wir im Browser tun können: Wir haben ein virtuelles DOM, das wir nach Belieben neu rendern können. Der Unterschied besteht darin, dass im Browser DOM-Updates implizit sein können. Auf dem Server müssen wir explizit angeben, wann wir in eine Zeichenfolge rendern, damit wir Aktualisierungen des virtuellen DOM basierend auf asynchronen Daten durchführen können.

Meine ideale Lösung, um dies zu lösen, wäre, das "Rendern" zuzulassen, das nur den Komponentenbaum erstellt, dann könnten wir den Baum durchsuchen, um Komponenten zu finden, die zusätzliche Daten benötigen, uns erlauben, mehr Daten asynchron zu laden und "neu zu rendern". " dieser Komponenten-Unterstruktur, und sobald wir zum Leeren des Markups bereit sind, erlauben Sie uns, diesen Baum in eine Zeichenfolge zu konvertieren. — @mridgway

Ja, das wäre gut. Derzeit kann als Workaround das zweimalige Rendern der Komponente auf einer Serverseite verwendet werden.

Ich möchte auf React -Nexus als Beispiel für das verweisen, was ich in React unterstützen möchte. Es ist im Wesentlichen eine Umschreibung der Funktionsweise von mountComponent außer dass es den Komponentenbaum aufbaut, ohne ihn tatsächlich in das DOM einzubinden oder einen String zu schreiben. Auf diese Weise können Sie den Komponentenbaum durchlaufen und asynchrone Methoden abfeuern, während der Baum aufgebaut wird. Das Problem mit dieser Implementierung besteht darin, dass sie diesen ersten Baum wegwirft und dann sowieso React.renderToString aufruft, während es schön wäre, diesen Pre-Render-Baum zu nehmen und ihn zu rendern/zu mounten.

Ich bin bereit, innerhalb des Kerns daran zu arbeiten, bräuchte aber einige Hinweise. Ich denke, eine der Anforderungen besteht darin, dass ReactContext nicht global referenziert wird, damit die Asynchronisierung keine Probleme verursacht. Gibt es andere Globals, die damit ebenfalls Probleme haben könnten?

@mridgway Wenn ich mich nicht irre, könnte global ReactContext eine Compat-Sache sein und in 0.14 verschwinden. Aber ich _könnte_ falsch liegen.

@gaearon Ja, das ist das Gefühl, das ich aus der Einstellung von withContext habe.

:+1: Verwenden von react-router für diesen Geldautomaten. Der Ansatz von

Eine etwas andere Sichtweise auf dieses Problem ist die Handhabung des asynchronen Ladens von Code-Chunks, die von einem Bundler (z. B. Webpack) erzeugt werden.

Wie in diesem Ticket besprochen – https://github.com/rackt/react-router/issues/1402 – besteht das Problem mit der Unterstützung für asynchrones Rendering darin, dass das anfängliche Rendering null zu sein scheint, da die relevanten Chunks im ersten noch nicht geladen wurden render pass, was zu einem <noscript> an der Wurzel des DOM und einem fehlgeschlagenen Prüfsummenfehler führt. Alles fügt sich danach schnell wieder, aber es führt zu einem Flackern in der Benutzeroberfläche bei lokaler Arbeit und schlimmer im Feld, insbesondere wenn die herunterzuladenden Chunks eine angemessene Größe haben (z. B. > 30 KB).

Die Möglichkeit, große Apps in mehrere Chunks aufzuteilen, ist uns wichtig, und nachdem wir das Problem beim Datenabruf gelöst haben (wir haben React-Router und verschachtelte Routen verwendet, die vor dem Rendern auf dem Server durchlaufen werden können, um Datenabhängigkeiten abzurufen), ist dies das letzte Stück des Rätsels, das uns daran hindert, vollständig zu einer React-Lösung für unser Frontend zu wechseln.

@anatomic Das liegt nicht in der Verantwortung von React, es ist Ihre Aufgabe, geeignete Chunks zu

Fühlen Sie sich frei, alternative Strategien zu implementieren, die möglicherweise besser zu Ihnen passen, z. B. <WaitFor for={MyAsyncLoadedCompSignal} until={...} then={() => <MyAsyncLoadedComp ... />} /> . Aber diese sind von Natur aus rechthaberisch und nicht etwas, was React bieten sollte oder muss, also überlässt man es am besten der Community.

Es ist besser, asynchrone Dinge außerhalb des Geltungsbereichs von React-Komponenten zu halten, hier ist ein Beispiel:

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

Siehe Webpack Code Splitting , React.js Routing from Scratch und -Routing (in Kürze verfügbar )

@syranide Ich werde weiter daran arbeiten, aber ich glaube nicht, dass es so binär ist, wie Sie es oben gesagt haben. Wir verwenden einen React-Router, was zu einigen Problemen in der Mischung führen kann, da der Router eine Komponente ist und nicht außerhalb der React-Umgebung sitzt.

Wenn wir den <WaitFor ... /> Ansatz gewählt haben, werden wir beim ersten Rendern sicherlich immer noch ein anderes DOM erhalten, das immer noch das Flackern/Verschwinden des Inhalts verursacht?

Wenn wir das gemacht haben Ansatz, werden wir beim ersten Rendern sicherlich immer noch ein anderes DOM erhalten, das immer noch das Flackern/Verschwinden des Inhalts verursacht?

Wenn Sie kein Flimmern wollen (einige tun es), müssen Sie einfach warten, bis alle Chunks, von denen Sie abhängig sind, geladen sind, bevor Sie rendern. Webpack bietet dies out-of-the-box mit require.resolve .

PS. Ja, der React-Router und was auch immer Sie sonst verwenden, verkompliziert die Lösung sicherlich, aber es ist immer noch nicht das Problem von React, das zu lösen ist.

Wenn Sie kein Flimmern wollen (einige tun es), müssen Sie vor dem Rendern einfach darauf warten, dass alle Chunks, von denen Sie abhängig sind, geladen sind. Webpack bietet dies out-of-the-box mit require.resolve.

Ich werde das untersuchen, mein Verständnis von require.resolve war, dass es eine synchrone Suche nach der ID eines Moduls war und keine Fahrt zum Server beinhaltete? Wir verwenden require.ensure , um unser Chunk-Laden zu verwalten.

Wenn wir uns unseren Code noch einmal ansehen, denke ich, dass wir das Problem umgehen können, indem wir den React-Router denken lassen, dass er auf dem Server rendert, aber dann dem standardmäßigen clientseitigen Ansatz folgt:

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

Ich werde das untersuchen, mein Verständnis von require.resolve war, dass es eine synchrone Suche nach der ID eines Moduls war und keine Fahrt zum Server beinhaltete? Wir verwenden require.ensure, um unser Chunk-Laden zu verwalten.

Entschuldigung, ja ich meinte require.ensure . Der Callback wird nur ausgeführt, wenn alle Abhängigkeiten erfüllt sind, also müssen Sie nur render/setState darin einfügen.

Ok, so haben wir es gemacht, danke für eure Antworten. Das fühlt sich an wie etwas, das im React-Router angesprochen werden muss, also werde ich die Diskussion dort fortsetzen - Entschuldigung, wenn dies der falsche Ort war, um diese Unterhaltung zu führen!

Sie haben Recht, dass require.ensure der richtige Weg ist, ich denke, unser ultimatives Problem war, dass es mit der gerade besuchten Route verknüpft werden sollte, also direkt mit dem Router verbunden ist. Da der React-Router komponentenbasiert ist, wird er an den Renderbaum gebunden. Ohne meinen obigen Hack müssen wir uns darum bemühen, den Baum anzuzeigen, bevor alles asynchron geladen wird (oder die Routing-Logik dupliziert wird, damit relevante Chunks auf der obersten Ebene geladen werden).

Sie haben Recht, dass require.ensure der richtige Weg ist, ich denke, unser ultimatives Problem war, dass es mit der gerade besuchten Route verknüpft werden sollte, also direkt mit dem Router verbunden ist. Da der React-Router komponentenbasiert ist, wird er an den Renderbaum gebunden. Ohne meinen obigen Hack müssen wir uns darum bemühen, den Baum anzuzeigen, bevor alles asynchron geladen wird (oder die Routing-Logik dupliziert wird, damit relevante Chunks auf der obersten Ebene geladen werden).

Ich bin mit React-Router nicht genau vertraut, aber ich stelle mir immer noch vor, dass es sich einfach um einen Fall von setRoute(...) => require.ensure([], function() { require(...); setRoute(...); }) was wirklich nicht praktikabel ist, also führen Sie eine andere Ebene der Indirektion ein. Eine Karte mit Routen und der asynchrone require.ensure Loader für jede. Schreiben Sie einen Helfer function asyncSetRoute(...) { loadRoute(route, function() { setRoute(...); }} , jetzt rufen Sie stattdessen asyncSetRoute an und es wird die Aktualisierung des Routers aufschieben, bis alles fertig ist.

Pseudocode und irgendwie generisch, aber das scheint mir der Gesamtansatz zu sein. Vielleicht sollte der React-Router dies bereitstellen, vielleicht nicht ... vielleicht wird es idealerweise als externer Helfer bereitgestellt, ich bin mir nicht sicher.

Nach stundenlanger Recherche bestätige ich gerade, dass serverseitiges Rendering _unmöglich_ ist, es

Mögliche kurzfristige Lösungen:
A. Füllen Sie einen Store vorab und machen Sie das serverseitige Laden synchron

B. Füttern Sie alles von oben als eine Dateneingabe, nachdem Sie Ihr einziges Datenobjekt asynchron abgerufen haben.

re: 'A'. Dies funktioniert nur, wenn Sie bereits wissen, wie Ihre Renderstruktur aussehen wird. Ich muss es rendern, um meine verschiedenen Sammlungen/Modelle/API-Abhängigkeiten zu kennen. Außerdem, wie kann ich das Abrufen auf dem Client asynchron, aber auf dem Server synchronisieren, ohne zwei verschiedene APIs zu haben?

re: 'B'. Im Prinzip das gleiche Problem wie oben. Müssen die Leute JSONs mit geringer Abhängigkeit erstellen, um mit jeder ihrer Routen zu gehen?

Gibt es andere kurzfristige Lösungen, während wir auf eine von React unterstützte Lösung warten? Irgendwelche User-Land Forks oder Plugins? https://www.npmjs.com/package/react-async ?

@NickStefan

Ich verstehe das Problem nicht. :-(

  1. Verwenden Sie Flux oder einen Flux-ähnlichen Container (Alt, Redux, Flummox usw.), wenn Geschäfte keine Singletons sind.
  2. Definieren Sie statische Methoden wie fetchData für Ihre Routenhandler, die Versprechen zurückgeben.
  3. Passen Sie auf dem Server die Route an die Komponenten an, nehmen Sie fetchData von ihnen und warten Sie, bis sie abgeschlossen sind, bevor Sie rendern. Dadurch wird Ihre Flux- oder Redux-Store-Instanz vorab gefüllt. Beachten Sie, dass die Store-Instanz kein Singleton ist, sondern an eine bestimmte Anfrage gebunden ist, sodass Anfragen isoliert bleiben.
  4. Wenn die Promises fertig sind, rendern Sie synchron mit der Store-Instanz, die Sie gerade vorausgefüllt haben.

Hier ist ein gutes Tutorial für Redux, das diesen Ansatz beschreibt: https://medium.com/@bananaoomarang/handcrafting -an-isomorphic-redux-application-with-love-40ada4468af4

@gaearon Ich entschuldige mich, wenn ich dich verwirrt habe. Danke für die Antwort. Aus Ihrer Liste klingt es so, als ob ich richtig liege in der Annahme, dass die Lösung für die Serverdatenabhängigkeit darin besteht, die Datenanforderungen nur an Ihrer Stammkomponente zu definieren (statische Methoden / der Artikel, auf den Sie verlinkt haben). Wenn Ihre Datenabhängigkeiten im Stamm definiert sind, ist es viel einfacher, Stores usw. vorab zu füllen.

Dies ist eine gute Flussmittelpraxis, aber ist sie nicht potenziell einschränkend? Wenn ich unten in Ihrem Ansichtsbaum eine kleine Komponente hinzufüge, die sehr unterschiedliche Daten benötigt, muss ich die Datenabhängigkeiten im Stamm bearbeiten.

Was ich verlange, ist eine Möglichkeit für tief verschachtelte Komponenten, um asynchrone Datenanforderungen zu definieren.

Wenn ich deren Anforderungen an die Root-Komponente hinzufügen muss, koppele ich dann nicht die Root an die eine Unterkomponente, die benötigt wird?

@NickStefan mit -Routing sieht der asynchrone Datenabruf beispielsweise so aus:

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

Diese Lösungen funktionieren jedoch nur, weil der gesamte Datenvorabruf an den Router gebunden ist. Das ist in den meisten Fällen kein Problem (es macht Sinn, URL definiert was auf der Seite stehen soll und somit welche Daten benötigt werden), aber im Allgemeinen ist es eine Einschränkung. Sie werden nie in der Lage sein, eine eigenständige wiederverwendbare Komponente (zB Twitter-Stream, Kommentare, Kalender) zu erstellen, die alles selbst handhaben würde und Sie müssen sie nur in die Komponentenhierarchie einfügen. Der einzige Weg, auf den ich gestoßen bin, der dies ermöglicht, ist Reaction-Async, aber das ist so ziemlich ein Hack.

Ich nehme an, dass React-Komponenten einfach nicht auf diese Weise verwendet werden sollen, sie sind immer noch mehr Ansichten als _Controller_-Ansichten. Auf der Grundlage von React wird wohl eine komplett neue Bibliothek entstehen müssen.

Mein Traum ist es, dass wir eines Tages ein komplettes CMS mit React schreiben können, etwa Wordpress, Drupal oder Modx. Anstelle von HTML/PHP-Schnipseln werden wir die Website jedoch aus React-Komponenten zusammensetzen.

@NickStefan

Aus Ihrer Liste klingt es so, als ob ich richtig liege in der Annahme, dass die Lösung für die Serverdatenabhängigkeit darin besteht, die Datenanforderungen nur an Ihrer Stammkomponente zu definieren (statische Methoden / der Artikel, auf den Sie verlinkt haben). Wenn Ihre Datenabhängigkeiten im Stamm definiert sind, ist es viel einfacher, Stores usw. vorab zu füllen.

Wenn ich deren Anforderungen an die Root-Komponente hinzufügen muss, koppele ich dann nicht die Root an die eine Unterkomponente, die benötigt wird?

Nicht direkt an der Wurzel – auf Route-Handler-Ebene. Ihre App kann viele Routen-Handler enthalten. Darüber hinaus unterstützen Bibliotheken wie React Router verschachtelte Routenhandler . Dies bedeutet, dass Ihr verschachtelter Route-Handler eine Abhängigkeit definieren kann und der Router-Match ein Array von übereinstimmenden hierarchischen Route-Handlern enthält, sodass Sie sie Promise.all können. Macht das Sinn?

Es ist nicht so granular wie „jede Komponente kann eine Abhängigkeit deklarieren“, aber ich habe festgestellt, dass es in den meisten Fällen alles ist, was ich möchte, den Datenabruf auf Routenhandler (oberste und verschachtelte) zu beschränken. Ich habe darunter reine Komponenten, die Daten über props akzeptieren, sodass sie nicht einmal _wissen_, dass sie abgerufen werden. Es macht Sinn, dass sie es nicht anfordern können.

Der Ansatz „jede Komponente kann eine Abhängigkeit deklarieren“ ist nicht praktikabel, es sei denn, Sie haben eine Art Batch-Lösung für Anforderungen. Stellen Sie sich vor, <User> erklärt, dass es einen API-Aufruf benötigt und Sie rendern jetzt eine Liste von 100 <User> s – möchten Sie auf 100 Anfragen warten? Diese Art der Anforderungsgranularität beim Abrufen von Daten funktioniert nur, wenn Sie über eine Lösung für die Batchverarbeitung von Anforderungen verfügen. Wenn das zu Ihnen passt, ist Relay genau das. Dafür bräuchte man aber ein spezielles Backend.

@NickStefan Wir unterstützen derzeit kein

@gaearon

Der Ansatz „jede Komponente kann eine Abhängigkeit deklarieren“ ist nicht praktikabel, es sei denn, Sie haben eine Art Batch-Lösung für Anforderungen.

Nach einigem Nachdenken stimme ich dir zu. Was ich suchte, ist nicht wirklich praktikabel.

Ich hatte ursprünglich den Gedanken, dass sogar deine 100Beispiel wäre in Ordnung mit Teamdisziplin (dh machen Sie einfach ein <UsersContainer> , das 1 Anfrage für alle 100 ausführt). Aber das ist nicht "menschenskalierbar", um nur eine Konvention zu haben. Es ist wahrscheinlich am besten, alle Abhängigkeiten zum Root oder zum Router zu erzwingen. Sogar Relay zwingt Sie mit seinen drei verschiedenen Containern (RelayContainer, RelayRootContainer, RelayRouter usw.) dazu, Abhängigkeiten im Root zu deklarieren. Es beweist irgendwie, dass der einzige Weg darin besteht, Ihre Abhängigkeitsdeklarationen im Wesentlichen in den Baum zu "hieven".

Hi!
Gibt es hierzu Updates?

+1

Ich behaupte, wenn Sie wirklich darüber nachdenken, möchten Sie dies nicht tun. Ich dachte ursprünglich, ich wollte, dass React asynchrones Rendering durchführt. Aber andere in diesem Thread haben mich vom Gegenteil überzeugt.

Sogar in Relay müssen Sie Ihre Abhängigkeiten im Grunde mit den Root-Container-Deklarationen, Relay-Containern usw. im Baum "heraufziehen". Synchrones Rendering ist der richtige Weg.

Asynchrones Rendering kann ein Albtraum sein. Ich spreche aus Erfahrung aus der Arbeit an einer Firmencodebasis mit Backbone, die gehackt wurde, um individuelle Updates in Request-Animationsframes durchzuführen.

Einverstanden. Früher dachte ich, dass wir dies brauchen, aber es ist eigentlich umgekehrt, wenn die Ansicht angibt, welche Daten geladen werden sollen. Die Ansicht ist eine Funktion des Anwendungsstatus, der selbst eine Funktion der Anforderung ist (und möglicherweise des Benutzerstatus, wenn eine Sitzung vorhanden ist). Darum geht es bei React; Komponenten zu erlauben, anzugeben, welche Daten geladen werden sollen, widerspricht der Idee des "einseitigen Datenflusses", IMO.

es ist eigentlich rückwärts, wenn die Ansicht angibt, welche Daten geladen werden sollen.

Ich bin mir nicht ganz sicher, ob das stimmt. Manchmal weiß nur die Komponente, welche Daten geladen werden sollen. Angenommen, Sie haben eine erweiterbare Baumansicht, die es einem Benutzer ermöglicht, ein riesiges Diagramm von Knoten zu durchsuchen - es ist unmöglich, im Voraus zu wissen, welche Daten geladen werden müssen; Nur die Komponente kann das herausfinden.

Unabhängig davon könnte diese Diskussion viel relevanter werden, wenn wir die Idee verfolgen, React-Code in einem Webworker auszuführen (#3092), bei dem asynchrone Kommunikation über die Brücke hinweg erforderlich ist.

Wenn ich im Beispiel einer großen Baumansicht wirklich in der Lage sein wollte, sie mit einem serverseitig bereits geöffneten Pfad zu rendern, würde ich diesen Pfad zur URL-Struktur hinzufügen. Gäbe es mehrere komplexe Komponenten wie diese, würde ich deren Zustände mit GET-Parametern darstellen. Auf diese Weise erhalten diese Komponenten alle Vorteile von SSR: Sie sind durchsuchbar, können mithilfe des Verlaufs navigiert werden, Benutzer können Links zu einem darin enthaltenen Knoten freigeben usw. Jetzt haben wir auch eine Möglichkeit für den Server, festzustellen, welche Daten benötigt werden geholt werden, um eine Antwort zu geben.

update Ich verwechsele ein Diagramm schlecht mit einem Baum, aber ich bin immer noch der Meinung, dass der Status der Benutzeransicht dieses Diagramms durch die URL-Struktur dargestellt werden sollte. Wusste auch nicht, dass Sie tatsächlich an React arbeiten. Wenn Sie an einem Framework arbeiten, um eine Datenschicht isomorph in die Ansicht zu integrieren, ist das großartig. Aber ich denke, wir sind uns einig, dass dies über den Bereich der Sichtweise hinausgeht und React nicht die Rolle eines Full-Stack-Controllers einnehmen sollte.

Habe nicht den ganzen Thread gelesen, sorry wenn das schon diskutiert wurde.

Eine Sache, die wirklich helfen würde, wäre, wenn es eine react-dom/server Methode gäbe, die nur eine Komponente "starten/instanziieren" und Lebenszyklusmethoden auslösen würde, aber den Entwickler entscheiden lassen würde, wann er bereit ist, die Komponente in einen HTML-String zu rendern . Beispielsweise könnte der Entwickler setTimeout oder etwas Zuverlässigeres verwenden, um auf den Abschluss der asynchronen Methoden zu warten.

Dies ist der "Hack", den ich derzeit mit Redux verwende, um dies zu erreichen:

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

Das Problem bei diesem Ansatz besteht darin, dass das zweite ReactDOM.renderToString alle componentWillMount Methoden erneut auslöst, was zu unnötigem Datenabruf führt.

Hallo Leute! wird daran gerade gearbeitet? Ich denke, dieses Thema gewinnt an Bedeutung. Ich habe kürzlich eine Bibliothek erstellt, die das connect von React Redux zu einem dataConnect in dem Sie auch die Containerdatenanforderungen angeben können. Diese Datenanforderungen werden dann zur Laufzeit gebündelt und mit GraphQL in einer einzigen Anfrage abgefragt. Ich denke, dies ist die Zukunft der Datenspezifikationen, da sie die Zusammensetzbarkeit und Isolierung fördert und auch ein performanteres Abrufen gewährleistet. (alle Konzepte auf Relais gesehen)

Das Problem mit dem oben genannten ist serverseitiges Rendering. Da ich nicht statisch analysieren kann, welche Datenanforderungen ich am Ende habe, müsste ich derzeit einmal rendern, um das Bundle zur Abfrage aus der DB zu erhalten, den Redux-Speicher verstecken und dann erneut rendern, um die endgültige Zeichenfolge zu erhalten . Das Problem ähnelt dem von @olalonde .

Es wäre fantastisch, Aktionen und Aktualisierungen des Baums der Reaktionselemente auslösen und das DOM-String-Ergebnis bei Bedarf abrufen zu können. So stelle ich es mir vor:

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

Eine andere Möglichkeit besteht darin, es einfach nach Belieben aktualisieren zu lassen, wie es auf der Clientseite der Fall wäre, wenn alle Hooks vorhanden sind und wie didMount funktionieren. Aber anstatt DOM zu mutieren, würde es nur die virtuelle Dom-Darstellung mutieren und bei Bedarf auch in String rendern.

Was denkt ihr? Etwas zu beachten oder sehe ich das komplett falsch?

Hallo zusammen. Ich habe diese Ausgabe jetzt seit einem Jahr oder so abonniert. Damals dachte ich, dass ich in der Lage sein muss, die Daten anzugeben, die aus Komponenten geladen werden sollen, da Komponenten meine Haupteinheit der Modularität waren. Dieses Problem war für mich sehr wichtig und ich dachte, dass es in der nächsten Version von React mit Sicherheit behoben werden würde.

Seitdem habe ich einen viel tieferen Respekt für die Ideale von REST/HATEOS entwickelt, da ein System von Anwendungen (Browser, Crawler, Anwendungsserver, Proxys, CDNs usw.) gefolgt. In Bezug auf dieses Problem sollte _die URL die einzig wahre Darstellung des Anwendungsstatus sein_. Sie und nur sie sollte bestimmen, welche Informationen zur Bearbeitung einer Anfrage erforderlich sind. Die Ansichtsebene React sollte dies nicht bestimmen; es sollte auf der Grundlage der Daten konstruiert werden. Die Ansicht ist eine Funktion der Daten und die Daten sind eine Funktion der URL .

Ich habe in der Vergangenheit gezögert, das hier rauszuwerfen, weil ich immer wieder höre, dass dies eine schöne Idee ist, aber dass die reale Welt einfach zu komplex ist, um damit zu funktionieren. Und gelegentlich höre ich von Beispielen, die mich zurücktreten lassen und mich fragen, ob ich zu pedantisch oder zu idealistisch bin. Aber während ich über diese Beispiele nachdenke, finde ich unweigerlich eine vernünftige Möglichkeit, den Anwendungsstatus in der URL darzustellen.

  • Hat Ihr Bundesland unabhängig voneinander variierende Parameter? Stellen Sie sie als separate Abfrageparameter dar.
  • Ist Ihr Bundesstaat (oder ein Teil davon) zu groß, um in eine URL zu passen? Benennen Sie es und speichern Sie es in einem unveränderlichen Datenspeicher. Verweisen Sie darauf mit Namen / ID.
  • Ändert sich Ihr Zustand so schnell, dass Sie nicht alles für immer speichern können? Herzlichen Glückwunsch, Sie haben legitime Big Data, können sich für die Speicherung entscheiden und beginnen, über clevere Dinge nachzudenken, die damit zu tun haben. Oder Sie können sich scheuen und einfach Ihre Daten mit UPDATE-Abfragen mutieren und akzeptieren, dass Sie nicht alle Dinge später für immer zwischenspeichern können (*).
  • Haben Sie Ansichten, die für verschiedene Benutzer unterschiedlich sind, aber unter derselben URL bereitgestellt werden, z. B. eine personalisierte Startseite? Navigieren Sie nach der Benutzeridentifikation zu einer URL, die die Benutzer-ID enthält.
  • Überbauen Sie eine ältere Anwendung, bei der Sie nicht die Möglichkeit haben, die alte URL-Struktur aufzubrechen? Das ist schmerzhaft, ich sitze im selben Boot. Leiten Sie die alte URL-Struktur in eine gut strukturierte URL-Struktur um und übersetzen Sie Sitzungsdaten (oder was auch immer) in URL-Pfadsegmente und -Parameter.
  • Haben Sie wenig oder keine Kontrolle über die Architektur Ihrer Anwendung? Das ist nicht der Fall, für den React entwickelt wurde, und wir möchten nicht, dass React verstümmelt wird, um es in so etwas wie eine gegenseitig kompatible Wordpress / Magnolia / Umbraco-Architektur einzupassen.

Was bekommen Sie für all die Arbeit, die Sie sonst nicht gehabt hätten?

  • Die Möglichkeit für einen Benutzer, einen anderen Benutzer an dieselbe Stelle in der Anwendung zu bringen, indem er eine URL freigibt.
  • Die Möglichkeit, mit allen Tools des Browsers in der Anwendung zu navigieren. Der Standard-Client ist in seinem Element.
  • Die Möglichkeit, komplexe Paginierungen auf eine für den Kunden leicht nachvollziehbare Weise anzubieten: ein next Link in einer Antwort. Die FB-Graph-API ist ein großartiges Beispiel dafür.
  • Eine detaillierte Grafik der Arbeitsabläufe Ihres Nutzers in Google Analytics.
  • Die Möglichkeit, diesen Graphen selbst aus nur Anforderungsprotokollen zu erstellen.
  • Eine Flucht aus der Star-Route des Chaos: Anstatt alle Anfragen als app.get("*", theOneTrueClientonaserverEntryPoint) , können Sie Ihr Server-Anwendungs-Framework so verwenden, wie es beabsichtigt war. Sie können korrekte HTTP-Statuscodes von Anfragen zurückgeben, anstatt 200 OK\n\n{status: "error"} . Die Sternroute ist verlockend, führt aber zum Wahnsinn.

Nun, da React, unser Star-Tool, den Betrieb nicht kontrolliert, wie erhalten wir dann unsere Daten? Woher wissen wir, welche Komponenten zu rendern sind?

  1. Leiten Sie zu einer Datenzugriffsfunktion weiter und rufen Sie Daten für die relevanten Anforderungsparameter ab. Wiederholen Sie den Vorgang, bis Sie die gesamte URL geparst haben und über ein vollständiges Kontextobjekt verfügen.
  2. Verwandeln Sie den Kontext in ein möglichst einfaches Antwortobjekt, als ob Sie auf eine API-Anfrage antworten würden. Bereiten Sie eine API-Anfrage vor? Dann sind Sie fertig und TROCKEN.
  3. Übergeben Sie dieses minimal komplexe, aber möglicherweise große Objekt an die Komponente der obersten Ebene. Von hier aus ist es die Zusammensetzung der React-Komponenten ganz nach unten.
  4. In String rendern, antworten. Teilen Sie Ihrem CDN mit, dass es dies für immer zwischenspeichern kann (* es sei denn, Sie haben diese Option oben geopfert), da CDN durch URLs identifiziert werden und alle Ihre Bundesstaaten eine URL haben. Sicher, CDNs haben keinen unendlichen Speicherplatz, aber sie priorisieren.

Ich will hier nicht trollen, aber ich bin der festen Überzeugung, dass das Kernthema der Anfragen in dieser Ausgabe fehlgeleitet ist und dass React nichts dafür implementieren sollte, zumindest nicht aus den Gründen, die ich hier oben gesehen habe das vergangene Jahr. Einige Aspekte einer guten Anwendungsarchitektur sind nicht offensichtlich, und das Web ist besonders schwer zu verstehen. Wir sollten von der Weisheit unserer Vorfahren, die das Internet und das Web aufgebaut haben, lernen und sie respektieren, anstatt der Bequemlichkeit halber auf Eleganz zu verzichten.

Das macht React großartig: Es ist die Aussicht. Die beste Aussicht. Nur die Aussicht. λ.

Muss dir @d4goxn widersprechen. Ich versuche nicht, React zu mehr als einer Ansichtsebene zu machen, und Routing ist wichtig, aber definitiv nicht genug, um alle Datenanforderungen zu spezifizieren. Durch die Angabe von Datenanforderungen auf Komponenten-/Containerebene machen Sie React nicht zu mehr als einer Ansichtsebene. Dies bietet Ihnen nur eine viel bessere Isolierung und einen besseren Workflow für Ihre App. Es ist nicht nur eine Vereinfachung der Arbeit, sondern eine Notwendigkeit für eine große Anwendung. Stellen Sie sich vor, meine App hat 30 Routen und ich möchte auf jeder eine Benutzerprofilkomponente hinzufügen. Wenn Sie einer Routenalternative folgen, bei der alle Datenanforderungen für jede angegeben sind, müssen Sie die 30 Routen durchlaufen, um diese Datenabhängigkeit hinzuzufügen. Wenn Sie mit Wenn Sie Ihre Datenanforderungen in einem Container für diese Komponente angeben, müssen Sie nur die Komponente hinzufügen, wo Sie möchten. Es ist Plug-and-Play. Darüber hinaus kann die Abfrage für alle Komponentendatenabhängigkeiten optimiert werden. Relay ist ein gutes Beispiel dafür, und die Gespräche darüber erklären dies viel besser als ich.

Ich habe großen Respekt vor alten Weisheiten, aber das sollte keine Grenze für die Evolution und das Schaffen neuer Standards sein, so sehe ich das zumindest.

Mein Vorschlag besteht im Grunde darin, React nicht zu ändern, sondern eine "nur virtuelle" Möglichkeit zu haben, den Dom/Komponenten-Baum zu ändern, im Grunde ein React, das auf der Serverseite "ausgeführt" werden kann, was meiner Meinung nach ziemlich einfach ist (nur Aktionen blockieren). um das DOM zu ändern). Sie können weiterhin Caching und ein CDN verwenden. Mit der wachsenden Dynamik von Sites und Anwendungen wird das statische Caching heutzutage tendenziell abnehmen, aber das ist ein anderes Thema 😄

Wenn die Ansicht Abhängigkeiten von Daten angibt, müssen Sie das Abhängigkeitsdiagramm optimieren und in eine minimale Anzahl von Abfragen umwandeln. Sie können die Daten nicht vorbereiten, bevor Sie die Ansicht erstellen, obwohl Sie sie in zwei Phasen aufteilen könnten, was meiner Meinung nach in diesem Thread geplant ist. Nehmen wir zum Beispiel eine Sammlung von mäßig komplexen Komponenten, die jeweils eine eigene Abfrage haben würden; Vielleicht sind sie nicht alle Instanzen derselben Komponente, und es gibt kein Muster, das in einer einzigen Abfrage zusammengefasst werden könnte. Ich nehme an, das ist es, was GraphQL anpackt. Sie müssen weiterhin GQL-Server für jeden Ihrer Datenspeicher implementieren oder integrieren. Herauszufinden, welche Teile der optimierten GQL-Abfrage von welchem ​​Datenspeicher verarbeitet werden sollen, klingt ziemlich komplex, aber mein Vorschlag hat auch etwas von dieser Komplexität.

In dem Beispiel, in dem eine große Anzahl von Routen alle dieselben Daten benötigen, würde ich dies eigentlich nicht als Grund sehen, die Kennung für diese Datenquelle aus der URL auszuschließen. Ich würde ein kleines Middleware-Modul zum Abrufen von Daten ziemlich nahe am Stamm des Stapels einhängen, das dieses Benutzerobjekt an einen Kontext anfügt und den Kontext dann auf seinem Weg zu einem Endrouten-Handler an weitere Middleware weiterleitet. Die Root-Komponente von React kümmert sich möglicherweise nicht um diesen bestimmten Teil des Kontexts, aber sie würde ihn an die nächste Ebene von Kindern weitergeben, die möglicherweise daran interessiert sind oder eigene Nachkommen haben, die sich darum kümmern. Wenn dies jedoch eine unangemessen komplexe oder tiefe Verkabelung mit sich bringt, ist möglicherweise so etwas wie ein Flux-Laden erforderlich, aber das ist ein großes Thema für sich.

Trennung und Isolation: Da spüre ich deinen Schmerz. In meinem Vorschlag haben wir zwei sehr unterschiedliche Systeme, die Daten von einem für Speicherung und Abruf optimierten Formular in ein für abstrakte Einfachheit optimiertes Formular umwandeln. Dann transformiert meine Ansicht das in eine Form, die zu ihm passt, da es in der Komponentenhierarchie weitergegeben wird. Diese beiden Systeme lose gekoppelt zu halten, während eine Funktion hinzugefügt wird, die Ergänzungen zu beiden Systemen erfordert, ist nicht das, was ich als hart bezeichnen würde, aber es ist auch nicht der Weg des geringsten Widerstands. Der mentale Wechsel zwischen der Programmierung für einen Datenspeicher und der Programmierung für eine dynamische Benutzeroberfläche ist real. Früher hatten wir separate Backend- und Frontend-Entwickler, und HTTP diente als Schnittstelle zwischen diesen beiden Paradigmen. Jetzt versuche ich, es in die eine Anwendung zu verinnerlichen, und Sie versuchen, es zu delegieren.

Ich bin nicht davon überzeugt, dass die zunehmende Komplexität und Aktivität innerhalb von Anwendungen verhindert, dass der Client-Zustand durch ein sehr kleines Objekt dargestellt wird, das auf einen viel größeren Datensatz auf dem Server verweist. Stellen Sie sich einen massiven Multiplayer-Ego-Shooter vor: Es passiert viel schnell, und Sie möchten die minimal erforderliche Menge an Informationen vom Client an den Host/Server des Spiels übertragen. Wie klein könntest du das bekommen? Eine Karte aller Eingabezustände; ein Zeitstempel + Unsicherheitsbereich, ein ~110-Bit-Feld für die Tastatur und einige Dutzend Bytes für die Maus-/Joystick- und VR-Headset-Ausrichtung. Der Client würde vorhersagen, optimistisch rendern und physikalisch-in-dern, und der Server würde eine riesige Menge an Informationen aus dem kleinen Zustand und der Zustandshistorie des Clients ableiten und eine ziemlich große Datenmenge zurückgeben, um alle Clients zu korrigieren und zu aktualisieren ( die gleichen Parameter für alle Agenten in der Nähe, Asset-Pushs), aber dieser Strom von Client-Anfragen könnte immer noch bequem in 2-KB-Anfragen passen. Und das könnte ein Segen für die Anwendungsarchitektur sein.

Ich werde Sie nicht bitten, mich in Relay und GraphQL zu unterrichten; Ich habe sie nur oberflächlich untersucht, bevor ihre APIs eine stabile Version erreichten (sind sie jetzt gesperrt?). ein guter Plan ist, dann ist es vielleicht an der Zeit, sie mir noch einmal anzusehen. Ich habe einige schwierige Fragen zu dieser Architektur, aber ich bin dabei, weit vom Thema abzuweichen.

:Biere:

PS Ich wollte nicht andeuten, dass HTTP ein gutes Kommunikationsprotokoll für ein MMOFPS ist

@d4goxn Ich verstehe Ihr Zögern und Ihre Skepsis

Ich glaube, dass diese Abstraktionsebene der richtige Weg ist. Ich habe es auf Relax CMS verwendet und der Workflow und die Isolation sind ein Vergnügen, damit zu arbeiten. Darüber hinaus entsprechen die angeforderten Daten nicht mehr und auch nicht weniger dem, was die Benutzeroberfläche benötigt, und das geschieht automatisch, indem die Daten, die jede Komponente benötigt, gesammelt und zusammengeführt werden. Dies wird von Relate http://relax.github.io/relate/how-it-works.html durchgeführt, wenn Sie dies überprüfen möchten. Damit kann ich auch jede beliebige Komponente überall in meine Anwendung einbinden und sicher sein, dass sie als eigenständiger Block meiner Anwendung funktioniert.

Das einzige, was noch herauszufinden ist, ist das serverseitige Rendering. Nachdem ich den Quellcode von React durchgesehen habe, glaube ich, dass es möglicherweise bereits eine Lösung gibt, wie ich sie beschrieben habe, wenn nicht, könnte ich an einer Lösung arbeiten, wenn jemand aus dem React-Team damit einverstanden ist.

@d4goxn Ich werde es auch wagen, mit dir zu streiten :) Dein ursprünglicher Beitrag enthält eine Aussage, dass die Ansicht eine Funktion der Daten ist und die Daten eine Funktion der URL sind . Ich würde das nicht gerade aufschlussreich nennen. Es gilt so ziemlich für jede Website oder jede Webanwendung, die nicht ganz zufällig ist (es stellt sich die Frage, ob beispielsweise der Zustand von geöffneten Dialogfenstern auch Teil der URL sein soll). Da wir alle Mathematik in der Schule hatten, wissen wir, dass Funktionen zusammengesetzt werden können. Die eigentliche Aussage könnte also sein, dass das ,

Für mich stellt sich die Hauptfrage , wie man eine solche Funktion konstruiert .

Der von Ihnen vorgeschlagene Ansatz fühlt sich den guten alten serverseitigen MVC-Anwendungen sehr ähnlich (zB Spring MVC ist ein gutes Beispiel). Die aktuelle URL aktiviert die entsprechende Controller-Methode, die _die Geschäftslogik übernimmt_. Es holt alle erforderlichen Daten und übergibt sie an die Ansicht. Mein Problem dabei ist folgendes: Wenn Sie sich die generierte Webseite ansehen, handelt es sich um eine Art Hierarchie von Komponenten (nicht unbedingt React-Komponenten). Was Sie tun müssen, ist, die Hierarchie aus der URL zu erstellen oder zu generieren. Aber Sie müssen es zweimal tun! Erstens müssen Sie die Hierarchie im Controller decodieren, um zu wissen, welche Daten Sie abrufen müssen, und zweitens müssen Sie die Hierarchie der Ansicht decodieren, um... nun... die eigentliche Ansicht zu rendern. Ich glaube nicht, dass es ein sehr _TROCKEN_ Ansatz ist.

Ich kenne mich mit Spring MVC nicht so gut aus, aber ich verwende sehr oft ein anderes MVC-Framework (PHP-Framework namens Nette), das dieses Problem auf eine sehr ähnliche Weise angeht, wie ich React verwenden möchte. Dieses Framework unterstützt auch Komponenten. Die Idee ist, dass Sie für jede Komponente (zB ein Formular) in Ihrem Code eine Factory definieren, die für die Instanziierung der Komponente und insbesondere für das _Laden aller notwendigen Daten_ verantwortlich ist. Eine solche Komponente kann dann überall in der Ansicht eingefügt werden. Wenn Sie also den HTML-Code schreiben und die _View-Hierarchie_ erstellen, können Sie einfach eine Komponente einfügen und die darunterliegende Factory kümmert sich um die notwendige Initialisierung. Die Komponente verhält sich wie eine kleine unabhängige Steuerung, hat einen eigenen Lebenszyklus, behandelt ihre Abhängigkeiten und kann sogar aus der Sicht parametrisiert werden, was die Wiederverwendbarkeit erhöht.

Ich habe diesen Ansatz mit React auch auf der Clientseite verwendet und er scheint sehr praktikabel zu sein. Die React-Komponenten wurden von Anfang an als _controller-views_ bezeichnet und so verwende ich sie. Ich habe meine _controller_-Komponenten, die für das Abrufen von Daten verantwortlich sind, und dann habe ich meine _view_-Komponenten, die sich nur um die Grafik kümmern. Die gesamte Seite besteht aus Komponenten beider Typen. Sie sind schön entkoppelt und leicht wiederverwendbar.

Meine Anwendung ist nicht isomorph (oder universell, wie sie heute genannt wird), da ich sie nicht brauchte, aber der Stack, den ich verwende, sollte dazu in der Lage sein (und ich glaube, dass dieser Stack, abgesehen von Relay, das noch experimentell ist, den Weg gegangen ist längster Weg in dieser Angelegenheit). Zur Info, so sieht es aus:

  • Das Kernwerkzeug ist React-Router . Es ermöglicht das Hooken von asynchronen Datenanforderungen für jede URL und funktioniert sowohl clientseitig als auch serverseitig. Formal gesehen leidet dieser Ansatz unter dem oben erwähnten Problem. Diese _controller_ Komponenten sind nicht möglich. Aber was hier zählt, ist das Design von react-router . Es erlaubt, sowohl die _Datenhierarchie_ als auch die _Ansichts-/Komponentenhierarchie_ an derselben Stelle zu definieren und die eigentlichen Routendefinitionen sind ebenfalls hierarchisch. Es fühlt sich sehr _React-like_ an.
  • Der gesamte Zustand (die Daten) wird mit redux behandelt . Neben allen anderen Vorteilen hält es den gesamten Zustand in einem einzigen Objekt, was bedeutet, dass es sehr einfach und natürlich ist, das Objekt auf dem Server zu erstellen und an den Client zu senden. Dank des connect() Ansatzes ist es auch nicht notwendig, alle Daten von der obersten Ebene nach unten mithilfe von Requisiten weiterzugeben (das wäre angesichts der aktuellen Größe meiner Anwendung absolut verrückt).
  • Ein weiteres Puzzlestück ist die Neuauswahl, die es uns ermöglicht, den Zustand so klein wie möglich zu halten. Und erhöht auch stark den Entkopplungsfaktor.

Es ist immer noch nicht perfekt, aber es ist ein bedeutender Fortschritt gegenüber dem, was verfügbar war, als ich zum ersten Mal auf diesen Thread stieß. Eine Beispielimplementierung finden Sie hier .

Ich habe auch fast alle isomorphen Arbeiten, außer dass ich eine Möglichkeit zum serverseitigen Rendering brauche, wenn Datenabrufe auf Komponentenebene erfolgen. Ohne die philosophische Argumentation jetzt hinzuzufügen, verwende ich Vanilla React (plus selbstgebautes Routing) seit langem erfolgreich mit Komponenten, die ihre eigenen Daten in componentDidMount abrufen. Ich mag die Idee, eine redundante Struktur zu pflegen, um meine Komponenten- und Datenabhängigkeiten zu definieren, wirklich nicht - das ist bereits in meinem Komponentenbaum definiert, und hier brauche ich nur irgendwie mehrere Renderdurchgänge, die Daten beim asynchronen Abrufen einbeziehen, bis die vollständige Komponente ist Baum wurde gelöst.

Im Moment wollte ich nur die wachsende Bedeutung dieses Aspekts für isomophe reagieren, imo. Ich werde versuchen, in der Zwischenzeit eine Lösung zu erarbeiten. Danke.

@decodeman Falls Sie interessiert sind, für Relate habe ich einen minimalen Reaction-Traverser erstellt, um Datenabhängigkeiten von den Komponenten zu erhalten https://github.com/relax/relate/blob/master/lib/server/get-data -dependencies.js. Auf diese Weise kann ich alle Datenabhängigkeiten abrufen -> Daten abrufen -> auf Zeichenfolge rendern reagieren.

@decodeman , FWIW wir haben den Double-Render-Ansatz (wie Sie anspielen) erfolgreich in einer großen Produktions-App verwendet. Es ist von diesem Redux-Saga-Beispiel inspiriert, aber der allgemeine Ansatz sollte mit jeder Art von asynchronem Laden gut funktionieren. Sie müssen nur das Laden auf componentWillMount . Um Hilfe halten es überschaubar, haben wir auch Komponenten höherer Ordnung , die von gemeinsamen Belastungsszenarien kümmern (zB Check für prop x und Last , wenn gleich Null.

Ich denke auch, dass es sehr wichtig wäre, Daten in Komponenten laden zu können und eine Art Lazy-Render-Methode zu verwenden. Um beide Seiten glücklich zu machen, erstellen Sie vielleicht eine neue Methode namens asyncRenderToString und fügen Sie ein neues "Lebenszyklus"-Ereignis namens asyncOnLoad oder so ähnlich hinzu. Dies wird nur aufgerufen, wenn Sie eine asyncRender-Methode aufrufen. In Bezug auf die Codeduplizierung zwischen Server- und Clientseite kann dies von Entwicklern behoben werden, indem sie einfach gemeinsamen Ladecode in eine separate Methode verschieben und diese Methode von asyncOnLoad und componentMount aufrufen. Um zu erkennen, ob asyncOnLoad ausgeführt wird, sollte wahrscheinlich ein zurückgegebenes Promise verwendet werden (wenn undefined zurückgegeben wird/Methode nicht definiert ist, sollte es die entsprechenden Lebenszyklusereignisse direkt aufrufen und dann rendern), andernfalls wird gewartet, bis das Promise aufgelöst wird.

Ich denke, eine solche Lösung würde alle hackigen Wege lösen, die wir derzeit für das richtige serverseitige Rendering verwenden müssen. Darunter zum Beispiel das einfache serverseitige Rendering von React-Anwendungen mit dem React-Router v4 (der keine zentralisierte Routenkonfiguration mehr verwendet, die derzeit die einzige Möglichkeit ist, isomorphe Anwendungen zum Laufen zu bringen)

Dies wurde durch den sogenannten Stream Check-Out-Reaktionsstream behoben.

Am Donnerstag, 3. November 2016, Florian Krauthan [email protected]
schrieb:

Ich denke auch, dass es sehr wichtig wäre, Daten innerhalb von laden zu können
Komponenten und haben eine Art Lazy-Render-Methode. Vielleicht um beides zu machen
Seiten glücklich erstellen eine neue Methode namens asyncRenderToString und fügen eine neue hinzu
"lifecycle"-Ereignis namens asyncOnLoad oder so ähnlich. Dieser Wille
nur aufgerufen werden, wenn Sie eine asyncRender-Methode aufrufen. In Bezug auf den Code
Duplizierung zwischen Server- und Clientseite kann von Entwicklern behoben werden
einfach den allgemeinen Ladecode in eine separate Methode zu verschieben und das aufzurufen
-Methode von asyncOnLoad und ComponentMount. Erkennen, ob asyncOnLoad ist
done sollte wahrscheinlich ein zurückgegebenes Promise verwenden (falls undefined is
zurückgegeben/Methode nicht definiert, es sollte direkt das zutreffende aufrufen
Lifecycle-Ereignisse und dann rendern) ansonsten wartet es bis das Versprechen
löst.

Ich denke, eine Lösung wie diese würde alle hackigen Wege lösen, die wir verwenden müssen
im Moment für korrektes serverseitiges Rendering. Darunter zum Beispiel erlauben
einfaches serverseitiges Rendering von React-Anwendungen mit dem React Router v4
(die keine zentralisierte Routenkonfiguration mehr verwendet, die derzeit die
einzige Möglichkeit, isomorphe Anwendungen zum Laufen zu bringen)


Sie erhalten dies, weil Sie diesen Thread abonniert haben.
Antworten Sie direkt auf diese E-Mail und zeigen Sie sie auf GitHub an
https://github.com/facebook/react/issues/1739#issuecomment -258198533,
oder den Thread stumm schalten
https://github.com/notifications/unsubscribe-auth/ATnWLUIEJw4m1Y3A4oGDOBzP6_ajDcqIks5q6g4_gaJpZM4CHAWq
.

@yamat124 wenn ich nach react stream suche finde ich nur dieses projekt: https://github.com/aickin/react-dom-stream

Zumindest basierend auf der Dokumentation gibt es keine Möglichkeit, ein Lebenszyklusereignis zum Vorladen von Daten zu haben, das den nächsten Renderaufruf für untergeordnete Elemente verzögert, bis das Versprechen gelöst ist. Oder redest du von etwas anderem?

Auch das sind alles sehr hackige Wege. Es erfordert, dass das Routing unabhängig vom Komponentenbaum ist. Und die "Main"-Komponente, die die Route enthält, muss alle Daten kennen, die geladen werden müssen (funktioniert in 99% der Fälle, aber nicht immer). Beides wäre obsolet, wenn React ein verzögertes Komponenten-Rendering hat.

Ich stimme zu, das ist wahrscheinlich das, was ich derzeit am meisten hasse, wenn ich reagiere. Ich hasse es, wenn Leute versuchen, das Framework davor zu schützen, diese Funktion nicht zu haben, indem sie sagen, dass es natürlich manuell gemacht werden kann, wie alles, was mit Softwareentwicklung zu tun hat, aber das bedeutet nicht, dass es sollte. Die Tatsache, dass es Unmengen von Modulen gibt, die dafür sorgen, dass genau dieses Ding für sich allein zeigt, dass es etwas ist, das innerhalb des Frameworks selbst gelöst werden sollte.

Next.js hat einen async getInitialProps serverseitige Rendering tun, leider immer noch nur brauchen , um die Anrufkinder getInitialProps von den Eltern den ganzen Weg zurück in die Wurzel. Der Apollo GraphQL-Client ermöglicht es auch, den gesamten Baum zu durchlaufen, um die Datenanforderungen serverseitig zu sammeln und dann alles zurückzusenden. Beispiel für Next + Apollo: https://github.com/sedubois/realate.

Es wäre toll, asynchrone React-Komponentenlebenszyklusmethoden zu haben, um diese Dinge miteinander zu kombinieren.

Habe gerade erst diese Woche von SSR erfahren, hoffe, was ich schreibe macht Sinn 😄

/cc @nkzawa @stubailo

Übrigens: componentWill/DidMount sind bereits asynchron, hilft das?

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

@sedubois wartet React, bis das Versprechen aufgelöst wird, bevor es

@olalonde es scheint so: 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 Beachten Sie, dass in Ihrem Beispiel componentWillMount

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

gibt undefined und ein Versprechen zurück, aber die Screenshots zeigen, dass React nicht auf das Versprechen wartet (es konnte nicht) , weil es den Loading... Text rendert.

Edit: Korrigiert.

@sedubois @polytypic Es führt auch dazu, dass alle innerhalb der Funktion geworfenen Fehler stillschweigend ignoriert werden, da das Versprechen überhaupt nicht behandelt wird.

@polytypic @dantman Ich bin mir nicht ganz sicher, was mit "das Versprechen wird überhaupt nicht behandelt" gemeint ist. Es wird vom Operator await erwartet. Sie müssen versuchen, den Fehler abzufangen, ich habe das Beispiel aktualisiert (mit einer Lade-/Fehlersignatur, ähnlich wie bei Apollo).

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

Und rendern:

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

@sedubois Wenn Sie eine Funktion async , gibt sie implizit ein Versprechen zurück, jedes throw in der Funktion wird implizit zu einer Ablehnung dieses Versprechens, anstatt den Stapel hochzuwerfen. Das bedeutet, dass componentWillMount jetzt eine Zusage an seinen Anrufer zurückgibt.

React macht nichts mit dem Rückgabewert, daher gibt Ihre Funktion jetzt ein Promise zurück, in der Erwartung, dass jemand etwas damit tut, aber es wird stattdessen einfach weggeworfen.

Da es nicht behandelt wird, bedeutet dies, dass jede Ablehnung dieses Versprechens von keinem Code in React abgefangen wird und alle nicht behandelten Fehler nicht in Ihrer Konsole angezeigt werden.

Und selbst ein Versuch..fangen wird Sie nicht perfekt davor schützen. Wenn setState einen Fehler in Ihrem Catch auslöst oder Sie einen Tippfehler im Catch machen, wird dieser Fehler stumm verschwinden und Sie werden nicht bemerken, dass etwas defekt ist.

@sedubois Eigentlich scheint eine asynchrone Methode in JavaScript (nun

Dennoch zeigen die Screenshots deutlich, dass React nicht auf die Auflösung des Versprechens wartet. Es ruft render direkt nach dem Aufruf von componentWillMount und rendert den Loading... Text. Dann ruft es render wieder nach dem Aufruf von setState . Wenn React auf die Auflösung des Versprechens gewartet hätte, würde es den Loading... Text überhaupt nicht rendern. Stattdessen würde es warten, bis das Versprechen aufgelöst wird, und erst dann render aufrufen, um die Art von Verhalten, um die es bei diesem Problem geht: um asynchrone E/A während des serverseitigen Renderings ausführen zu können.

@dantman @polytypic danke 👍 Ja, sicher sind Änderungen in React erforderlich, um auf die Antwort zu warten und Fehler richtig zu behandeln. Vielleicht könnte das Hinzufügen von await hier und da in seinem Code es tun 😄

Persönlich verwende ich Web Worker, um den Anwendungsstatus zu speichern und Props bei Änderungen auszugeben (da ich die Datenspeicherung und das Abrufen von React-Komponenten vollständig getrennt habe und React nur als Renderer behandle, um das unidirektionale Flussprinzip beizubehalten), also wartet es einfach, bis a Nachricht mit den erwarteten Anforderungen (z. B. wenn eine bestimmte Eigenschaft wahr ist) zu rendern.

Auf diese Weise werden die ersten Nachrichten selbst dann nicht für die statischen serverseitigen Renderings verwendet, wenn asynchrone Komponentenmethoden erforderlich sind, auch wenn es sich bei den ersten Nachrichten um zwischengeschaltete "Ladezustände" handelt.

@sedubois Es müsste ein anderer Stapel von Methoden sein; oder eine brechende Veränderung. Die aktuellen Rendermethoden von React sind synchronisiert, daher benötigen wir eine andere Server-Rendermethode, die ein Promise zurückgibt.

@dantman Ich möchte hier keine Meinung

Ich mag, was @fkrauthan vorgeschlagen hat. Eine Lebenszyklusmethode load die ein Promise oder undefined und eine zusätzliche asynchrone renderToStringAsync Funktion zurückgeben kann. Aber load sollte immer aufgerufen werden, sowohl auf dem Client als auch auf dem Server. Wenn wir render oder renderToString das zurückgegebene Promise ignoriert, um unserem heutigen Verhalten zu entsprechen. Wenn wir renderToStringAsync und load ein Promise zurückgibt, muss das Promise vor dem Rendern aufgelöst werden.

Warum nicht wie Next.js eine React async getInitialProps Lifecycle-Funktion hinzufügen, die vor dem Konstruktor aufgerufen wird? Wenn es keine solche Methode gibt, rufen Sie den Konstruktor direkt auf.

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

Es sieht so aus, als ob Next.js die Aufgabe erledigt, außer dass sein getInitialProps nicht in den Lebenszyklus der React-Komponente eingebunden ist, sodass es nur auf der Komponente der obersten Ebene aufgerufen werden kann. Die Ideen sind also da und könnten einfach zusammengefügt werden?

@sedubois Ich glaube nicht, dass getInitialProps der richtige Ort ist. Zuallererst haben die Leute, die ES6 verwenden, diese Methode nicht. Zweitens möchten Sie nicht an den initalProps (das sind nur die Standardeinstellungen) arbeiten, sondern auf der Zusammenführung von initalProps + übergebenen Requisiten arbeiten. Daher mein Vorschlag für ein neues Lebenszyklusereignis, das vor componentWillMount versendet wird.

Ich denke, die Idee von @ Lenne231 könnte einige Probleme verursachen, da die Leute dort andere Lebenszyklusereignisse weitergeben könnten, damit dieses Versprechen bereits gelöst ist. Da nun zwei unterschiedliche Verhaltensweisen desselben Lebenszyklusereignisses vorliegen, wird es etwas schwierig.

Daher sollte es auf dem Server und auf dem Client eine Sync-Render-Methode geben, die diesen neuen Lebenszyklus überhaupt nicht aufruft. Und eine asynchrone Version, die diesen Lebenszyklus aufruft und immer wartet, bis die Zusage gelöst ist, bevor sie fortfährt. Das würde Ihnen meiner Meinung nach die größte Flexibilität geben, da Sie jetzt entscheiden können, ob Sie diesen Lebenszyklus auch für Ihr Client-Rendering oder nur für Ihr Server-Rendering benötigen.

Leute, die ES6 verwenden, haben/verwenden diese Methode nicht

@fkrauthan vielleicht beziehen Sie sich auf getInitialState ? Hier sprach ich von einem neuen, getInitialProps (der Name, der in Next.js verwendet wird).

Was Ihren zweiten Punkt angeht, ja, es könnte besser sein, die neue Lebenszyklusmethode vor componentWillMount und nicht vor constructor . Ich denke, bei Next ist das nicht so, weil sie nicht den Luxus haben, den React-Lebenszyklus wie bereits erwähnt zu optimieren. Daher wäre es großartig, diese Ideen in React einzubringen.

@sedubois Sogar das. Ich verwende zum Beispiel die es7-Funktion und definiere static props = {}; in meinem Klassenkörper. Das macht den Code meiner Meinung nach viel angenehmer zu lesen und ich bin sicher, dass immer mehr Leute darauf umsteigen werden, sobald es7 offiziell veröffentlicht wird.

Hinweis: Klasseneigenschaften sind keine "ES7"-Funktion, da ES7 bereits fertiggestellt wurde und die einzige neue Sprachfunktion dort der Potenzierungsoperator ist. Was Sie meinen, ist der Vorschlag für Klasseneigenschaften der Stufe 2. Es ist noch nicht Teil der Sprache und kann sich ändern oder wegfallen.

Ich sehe keine Notwendigkeit für asynchrone Funktionen in React zum Abrufen von Daten, stattdessen wäre ich mit einem virtuellen Renderer zufrieden, der ToString bei Bedarf rendert. Sie könnten also Aktionen festlegen und der virtuelle Baum würde sich entsprechend aktualisieren, bevor er in einen String gerendert wird. Dies wäre meiner Meinung nach ideal, um einen guten SSR in der Reaktion zu erreichen. Überlässt dem Entwickler, wie und wo die Daten abgerufen werden.

Ich habe es bereits erwähnt, aber eine gute API wäre etwa die folgende (mit einem kleinen Update mit dem, was ich zuvor vorgeschlagen habe):

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

Dies würde das Problem vermeiden, das einige GraphQL-Clients wie Apollo haben, die sie erzwingen, einen doppelten renderToString zu erstellen. Der erste, der Datenabhängigkeiten abruft, und der zweite, der mit den Daten aufgefüllt wird. Ich denke, dies ist eine ziemliche Verschwendung von Verarbeitung, da renderToString ziemlich teuer ist.

@bruno12mota Ich

@fkrauthan ja, aber virtuelle renderToString React zu verbessern, aber es gibt keinen wirklichen Fortschritt in dieser Angelegenheit seitens des React-Teams (keine Kritik, sie machen einen großartigen Job).

Ich stimme @bruno12mota zu , da ich einiger Zeit mit diesem Thema beschäftigt habe, das ist der einfachste Ansatz. Es lässt dem Entwickler die Freiheit zu bestimmen, wann sein Rendering "flush" soll.

Es macht alles viel komplizierter.

1.) Aus Code-Sicht (ich habe den Code durchgesehen und das Asynchronisieren des Renderns sollte viel einfacher sein, als einen Renderer zu erstellen, der nur VDom verwendet und dann an einem Punkt ausgegeben werden kann)
2.) Jetzt müssen Sie auch Unmount-Ereignisse überdenken. Und es macht den benutzerdefinierten Code viel komplizierter als nötig. Beispielsweise kann das Laden von Daten dazu führen, dass eine neue Komponente gerendert wird, die auch Daten laden muss. Jetzt muss die Lib plötzlich herausfinden, welche Komponente an welcher Position bereits geladene Daten enthält und welche Komponente neue Daten enthält und so weiter.

Ich bin bei @bruno12mota. Ein VDom ist wohl eine eher "low-level"-Lösung, wäre aber für React-Entwickler wahrscheinlich einfacher zu implementieren, hätte keine Abwärtskompatibilitätsprobleme und könnte es der Community ermöglichen, mit verschiedenen High-Level-Lösungen zu experimentieren.

Eine Lösung auf höherer Ebene könnte beispielsweise eine "Komponente auf höherer Ebene" sein, die asynchrone componentWillMount Methoden umschließt, auf alle ausstehenden Zusagen wartet und VDom.renderToString() auslöst, wenn alles gelöst ist. Ich verwende diese Art von Strategie für SSR in meiner App, obwohl ich derzeit leider viel überhole, da es kein VDom gibt.

Ich denke, der beste Weg wäre, keine Funktionen zu ändern, die wir derzeit haben. Das bedeutet, dass sich getInitialState, componentWillMount oder renderToString nicht anders verhalten.

Stattdessen sollten wir neue Funktionen hinzufügen, um das hier diskutierte Problem zu lösen. So wie es Funktionen gibt, die nur auf dem Client aufgerufen werden, kann es Funktionen geben, die nur auf dem Server aufgerufen werden.

Ich denke, wir könnten eine Funktion "renderToStringAsync" zu "react-dom/server" hinzufügen. Der Unterschied zum normalen renderToString wäre, dass es ein Promise zurückliefert.

Auf der Implementierungsseite besteht der einzige Unterschied darin, dass die asynchrone Version immer dann, wenn sie eine Komponenteninstanz erstellt, zuerst "initialize()" aufrufen und darauf warten würde, bevor sie render() aufruft. Wenn die Komponente keine "initialize"-Methode hat, könnte sie sofort render() aufrufen.

Wenn wir also die folgende Beispiel-App-Struktur haben:

    ComponentB
    ComponentC
        ComponentD
        ComponentE

der prozess wäre:

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

Im Grunde brauchen wir also nur eine neue Funktion "renderToStringAsync", die eine neue, optionale Funktion "initialize" verwendet. Es wäre nicht so schwer.

@VanCoding hat die gleiche Idee, über die ich schon seit einiger Zeit nachdenke. Ich habe mich gefragt, ob initialize auch automatisch in componentDidMount auf dem Client aufgerufen werden kann?

Auf dem Server wäre es also:

initialize()
render()

Und auf dem Client wäre es:

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

Ich habe eine funktionierende Implementierung von React Server Rendering mit React-Router v4 und React-Apollo mit GraphQL.

es wurde von serverseitigem-rendering-code-splitting-and-hot-reloading-with-react-router inspiriert.

Grundsätzlich verwendet das Server-Rendering eine synchronisierte Komponente, das Client-Rendering eine asynchronisierte Komponente, sodass das Webpack das asynchrone Laden und Ausführen des Moduls Javascript ermöglicht.

Das Server-Rendering wird übrigens mit der Nashorn Script Engine (JVM) realisiert.

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

Server-Rendering-Code
https://github.com/shendepu/react-ssr-starter-kit/blob/apollo/src/main.js#L128 -L155

Es gibt einen wichtigen Punkt, um synchronisierte und asynchronisierte Komponenten in der Route zu unterscheiden.
Synchronisierte Komponente in Route
https://github.com/shendepu/react-ssr-starter-kit/blob/apollo/src/routes/Counter/Route.js#L10

Asynchronisierte Komponente in RouteAsync
https://github.com/shendepu/react-ssr-starter-kit/blob/apollo/src/routes/Counter/RouteAsync.js#L7 -L23

HINWEIS: Der Dateiname MUSS Route.js oder RouteAsync.js lauten. Der Code sollte immer RouteAsync.js importieren. Webpack ersetzt es durch Route.js, wenn es für das Server-Rendering erstellt wurde.
https://github.com/shendepu/react-ssr-starter-kit/blob/apollo/build/webpack.config.js#L72 -L80

Es wird also zwei Versionen von Build dist und dist_ssr , dist ist für den Client und dist_ssr für den Server, der nur zwei Teile hat: Vendor und App . Der Speicherstatus wird exportiert und in <script> von index.html als __INITIAL_STATE__ eingebettet. Der Grund für zwei Build-Versionen ist, dass ReactDomServer.renderToString() die asynchrone Komponente noch nicht unterstützt.

Das einzige Problem beim Ansatz mit zwei Versionen ist, dass im Entwicklungsmodus eine Warnung angezeigt wird:
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:

Da der einzige Unterschied zwischen Client- und Serverversion jedoch Route.js und RouteAsync.js ist und die Warnung daher ignoriert werden kann, wird die Warnung im Produktionsmodus nicht angezeigt.

Für den asynchronen Datenabruf vor dem Komponenten-Rendering auf der Serverseite:

  • Rest API: Statische loadData in der Komponentenverwendung hinzugefügt
const { matchedRoutes, params } = matchRoutesToLocation(rootRoute.routes, 
                                                        location, [], {}, rootRoute.pattern)
matchedRoutes.filter(route => route.component.loadData).map(route =>
              route.component.loadData(store, params))

um loadData zu prüfen und auszuführen. matchedRoutes ist ein Array von übereinstimmenden Routen, deren Eltern aus dem Index 0 . loadData kann nacheinander oder parallel ausgeführt werden.

  • GraphQL-Abfrage: Verwenden Sie einfach getDataFromTree von react-apollo/server , um alle GraphQL-Abfragen auszuführen.

Übrigens: Die Verwendung von Nashorn Script Engine bietet eine Möglichkeit zur Leistungsoptimierung: Da der Anbieter Bibliothekscode und Polyfills enthält, die einmal ausgeführt und wiederverwendet werden, wird für spätere Anfragen nur App-Chunk ausgeführt. Da Bibliotheken in den Vendor-Chunk verschoben werden, enthält die App nur Javascript-Code in src, der klein ist, sodass dies im Vergleich zur Bewertung jedes Vendor-Chunks eine Leistungssteigerung darstellt. (Alles Javascript wird einmal kompiliert und zwischengespeichert und bei jeder Anfrage ausgeführt.)

Ich habe gelogen, dass der App-Chunk nur src-Code enthält. es enthält auch babel-runtime und doppeltes core-js/library auf das babel-runtime verweist. babel-runtime kann nicht in den Vendor Chunk verschoben werden, da es keinen index.js oder Haupteintrag hat, so dass Webpack es nicht als Modul erkennen konnte. aber die Codegröße ist klein.

Lassen Sie uns das Beispiel von @VanCoding weiter https://github.com/facebook/react/issues/1739#issuecomment -261577586

Vielleicht ist initialize kein guter Name – der Name sollte irgendwie deutlich machen, dass er nur auf dem Server oder in einer Umgebung verwendet werden soll, in der das Rendering verzögert wird, bis diese Funktion zurückkehrt – also nicht im Client. Ideen: getStaticProps , getServerProps , getInitialProps , getAsyncProps ...

Es wäre auch gut, vom Kernteam zu hören, ob es technische oder andere Hindernisse bei der Entwicklung einer solchen renderToStringAsync-Methode gibt.

@nmaro Nun, ich denke, die Funktion sollte nichts zurückgeben, daher würde ich einen Namen bevorzugen, der nicht mit "get" beginnt, aber abgesehen davon denke ich immer noch, dass dies der beste Weg wäre. Es stört mich nicht, wie es am Ende heißt.

Außerdem habe ich dafür einen Proof of Concept in etwa 40 Codezeilen erstellt. Es ist nicht für den produktiven Einsatz gedacht, aber meine Tests mit verschiedenen Komponenten waren erfolgreich.

Wenn das Reaktionsteam dies also immer noch nicht wünscht, wäre es nicht so schwer, dies in einer Bibliothek von Drittanbietern zu tun.

Alternativ könnten wir eine Option zu renderToString hinzufügen, wie in
renderToString(Component, {async: true})

@nmaro Das würde den Rückgabetyp der Funktion von den bereitgestellten Optionen abhängig machen, was meiner Meinung nach keine gute Praxis ist. Ich denke, eine Funktion sollte immer ein Ergebnis des gleichen Typs zurückgeben.

@VanCoding haben Sie ein Beispiel dafür, wie Sie Ihren Code als Bibliothek eines Drittanbieters verwenden können?

@ VanCoding cool! Aber ehrlich gesagt denke ich, dass es schwierig sein wird, dies in den internen Rendering-Algorithmus von React zu integrieren. Schauen Sie sich https://github.com/facebook/react/blob/master/src/renderers/dom/stack/server/ReactServerRendering.js an und verlieren Sie sich in den Interna von React... IMO brauchen wir wirklich eine Meinung von jemandem von der Ader.

@sedubois Sie können die Funktion wie die normale renderToString-Funktion verwenden. Aber es gibt ein Promise zurück, das in einen String aufgelöst wird, anstatt einen String direkt zurückzugeben.

Die Verwendung würde also so aussehen:

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

Theoretisch sollte es in der Lage sein, jede vorhandene Reaktionskomponente auf die gleiche Weise wie der normale renderToString zu rendern. Der einzige Unterschied besteht darin, dass auch asynchrone Komponenten unterstützt werden.

@nmaro Du hast recht. Das wäre hilfreich.

Wie ist der Stand dazu?

@firasdib Wir warten immer noch auf einen Kommentar einiger reagierender Entwickler, denke ich.

+1

Google-Indexierungsprobleme erfordern SSR und das ganze unten beschriebene Gepäck müssen wir bewältigen. Daher ist dieses Problem für React sehr wichtig, um als allgemeine Bibliothek erfolgreich zu sein.

Ich denke, das gesamte asynchrone Rendering ist nicht erforderlich, wenn JavaScript eine angemessene Möglichkeit bietet, auf ein Versprechen zu warten. Dann könnten wir einen Parameter übergeben, der angibt, ob das Wetter synchron (für SSR) oder asynchron (für einen echten Webbrowser) geladen werden soll. Die Datenladelogik könnte im Konstruktor einer beliebigen React-Komponente ein Promise starten und in componentWillMount warten.

Wie ich jedoch verstehe, haben die React-Entwickler Zweifel an der Nützlichkeit von ComponentWillMount gezeigt und empfohlen, den Konstruktor die ganze Arbeit machen zu lassen. So oder so wird es funktionieren.

Einige Leute haben argumentiert, dass das Problem dadurch behoben wird, dass Komponenten reine Ansichten sind. Sie sind teilweise richtig. Leider funktioniert dies nicht im allgemeinsten Fall, in dem wir nicht wissen, welche Daten geladen werden sollen, bis der Renderbaum fertig ist.

Der Kern des Problems besteht darin, dass die Renderfunktion über eine Logik verfügen kann, um auszuwählen, welche Komponenten zum DOMTree hinzugefügt werden sollen. Das macht React so leistungsfähig und für serverseitiges Rendering so schlecht geeignet.

IMO ist die wahre Lösung, alle ausgestellten Datenladeversprechen bei Reagieren registrieren zu können. Sobald alle Datenladeversprechen abgeschlossen sind, ruft uns die Reaktion mit dem Renderergebnis zurück. Dies hat eine Reihe von Implikationen:

  • der DOMtree wird mehrmals gerendert. Es ist wie ein Live-System, das sich von einem instabilen Datenladezustand in einen stabilen "geladenen" Zustand "beruhigt".
  • Es besteht die Gefahr, dass das System in einem fehlerhaften System nicht in einen stabilen "geladenen" Zustand gelangt. Um damit umzugehen, muss eine Auszeit eingeführt werden.

Das Ergebnis ist eine viel "rechenintensivere" Nutzung der Bibliothek auf der Serverseite mit einem nicht schnellen Prozess.

Daher denke ich immer noch an eine Problemumgehung, die den generischen Fall zugunsten der Erledigung der Dinge kompromittiert :)

In der Zwischenzeit müssen wir SSR in einer asynchronen Datenladeumgebung lösen.

  1. Aufrufe zum Laden von Daten müssen von der Anforderungs-URL abgeleitet werden (react-router ist ein Beispiel). Sammeln Sie alle Zusagen zum Laden von Daten in einer einzigen Liste von Zusagen.
  2. Verwenden Sie Promise.all(), was an sich schon ein Versprechen ist. Wenn diese Zusage abgeschlossen ist, übergeben Sie geladene Daten an die Server-Rendering-Funktion.

Dieses Modell ist nicht an eine bestimmte Designarchitektur gebunden. Es scheint, dass Flux/Redux etwas davon profitieren könnte, aber ich bin mir nicht sicher, da ich das Redux-Modell in meiner Anwendung aufgegeben habe.

Das Problem in diesem Modell ist Punkt Nr. 1 oben. Eine einfache Anwendung kennt alle Aufrufe zum Laden von Daten. In einer komplexen Anwendung mit unabhängigen "Modulen" erfordert dieses Modell eine Art Replikation des ursprünglichen React-Komponentenbaums und sogar der Renderlogik.

Ich sehe nicht, wie SSR für das komplexe Projekt verwendet werden kann, ohne die bereits in der render()-Funktion geschriebene Logik zu wiederholen. Flussmittel könnte eine Lösung sein, aber ich habe keine Erfahrung damit.

Die Implementierung von Punkt 1 ist vage. Im Beispiel eines React-Routers müssen wir als Router die Komponenten der obersten Ebene an uns zurückgeben, die zu dieser Route gehören. Ich denke, wir können diese Komponenten instanziieren:

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

Komponenten der obersten Ebene sind also keine "reinen" Komponenten mehr. Diese Komponenten spielen jetzt eine Rolle als Controller der obersten Ebene, da sie das Laden von Daten auslösen können. Das Flux-Modell macht es nicht besser. Es verschiebt einfach die Route zur Datenladefunktionszuordnung in ein anderes Modul.

Leider führt die Fortsetzung des Ladens von Daten auf diese Weise zu den Kindern der oberen Controller zu einer Duplizierung des Objektgraphen, der für Reaktionsrenderingzwecke erstellt wurde. Um die Dinge einfach zu halten (wenn möglich), müssen wir alle Datenladungen auf die oberste Controller-Ebene legen.

Das sehe ich.

Wenn ich diese SSR-Implikationen erkannt hätte, hätte ich zweimal über die Nützlichkeit von React für indizierte Google-Anwendungen nachgedacht. Flussmittel ist vielleicht eine Lösung, aber Flussmittel machen die Anwendung noch komplizierter als eine einfache React-Anwendung. Ich war dort. Anstelle einer einfachen Datenladefunktion muss ich meine Logik über mehrere Dateien hinweg verfolgen. Auf theoretischer Ebene sah es wirklich sehr gut aus, bis ich ein Projekt am Laufen hatte. Neue Leute hatten es schwer, loszulegen. Kein solches Problem, nachdem Redux aus der Codebasis gezogen wurde. Ich dachte, es wäre die Antwort auf alle Komplexitäten des UI-Designs :)

Ab der dieswöchigen React Conf , nachdem React Fiber (React 16) herausgekommen ist, planen sie, an Folgendem zu arbeiten (angeblich für React 17):

screen shot 2017-03-16 at 15 55 51

Bedeutet, dass asynchrone React-Lebenszyklusmethoden kommen könnten?

Nun, geht es bei dem Faserzeug nicht hauptsächlich um schnelleres/glatteres Re- Rendering? Auf dem Server rendern wir nur einmal, daher bin ich mir nicht sicher, ob diese Änderungen Auswirkungen auf das serverseitige Rendering haben.

@VanCoding hat meine obige Nachricht aktualisiert, die etwas unklar war. Ich meinte ihre Pläne für die Zeit, nachdem die Glasfaser ausgefallen ist.

Für Leute, die wie ich hier landen, habe ich am Ende eine benutzerdefinierte Implementierung mit React erstellt, die hilfreich sein könnte.

https://github.com/siddharthkp/reaqt

der Schlüssel ist asyncComponentWillMount, der nur auf dem Server ausgeführt wird. es ist nur für Einstiegspunktkomponenten verfügbar, nicht für alle Komponenten, um die hier erwähnten Probleme zu vermeiden.

@siddharthkp Ich verstehe nicht wirklich, was Ihre Komponente zu lösen versucht? Wenn es nur asyncComponentWillMount für die Top Level Application React-Komponente unterstützt, warum sollte ich dann Ihre Bibliothek überhaupt verwenden? Ich könnte dieses Versprechen einfach draußen lösen und dann rendern aufrufen? Oder habe ich etwas übersehen?

@fkrauthan

Ich könnte dieses Versprechen einfach draußen lösen und dann rendern aufrufen?

Du hast Recht, genau das tut es.

Der Reiz besteht darin, dass Sie async ssr (+ code splitting + hmr) mit einem Befehl erhalten. Ich habe viele Leute gesehen, die SSR nicht implementiert haben, weil es nicht so einfach ist, und das war die Motivation.

es unterstützt nur asyncComponentWillMount für die Top Level Application React-Komponente

  1. Erfordert nicht eine Anwendungskomponente. Sie können Daten für jede Seite/jeden Bildschirm/jeden Einstiegspunkt abrufen. (Förderung mehrerer Einstiegspunkte hier )

  2. Das Muster ist beabsichtigt, wenn jede Komponente im Baum ihre eigenen Daten benötigt, könnten wir am Ende ein schlechtes Muster für die Leistung fördern. Das Abrufen von Daten auf der Komponente der obersten Ebene und das Übergeben an untergeordnete Elemente als Requisiten hält sie in Schach.

@siddharthkp wie verbessert sich Ihr Repo gegenüber Next.js (das übrigens auch auf der React Conf präsentiert wurde)?

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

Wie auch immer, Diskussionen über Bibliotheken von Drittanbietern sind IMHO off-topic, was uns interessiert, ist die Unterstützung innerhalb von React selbst.

Diskussionen über Bibliotheken von Drittanbietern sind IMHO off-topic, was uns interessiert, ist die Unterstützung innerhalb von React selbst.

Ich stimme vollkommen zu. Kann spezifische Fragen in reaqt-Problemen besprechen

Ein Anwendungsfall, den ich dafür habe, ist die Verwendung von reaktiven nativen und reaktiven nativen Vektorsymbolen. Ich möchte, dass die Requisite einer Komponente durch die Daten bestimmt wird, die von einer Zusage erhalten wurden

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

Wenn dies unterstützt wird, würde dies meiner Meinung nach viel komplexen Code verständlicher machen und möglicherweise neue asynchrone Muster eröffnen.

Wir haben das gleiche Problem, ich möchte die SSR ausführen, nicht nur die vorhergesagten Requisiten durchsuchen, sondern auch den Inhalt, der von der Aktion in ComponentWillMount abgerufen wird. Wie kann ich warten, bis die Requisiten vorbereitet sind, und dann die Requisiten in Zeichenfolge rendern.

Wenn jemand dies nützlich findet, habe ich eine Bibliothek namens React-Frontload geschrieben , mit der Sie eine Promise-Returning-Funktion an eine Komponente binden können, die ausgeführt wird, wenn die Komponente rendert, sowohl auf dem Server als auch auf dem Client.

Die Funktion läuft 'synchron' beim Server-Rendering (naja, nicht wirklich ;-), aber das Rendern der letzten Komponente wird blockiert, bis es aufgelöst wurde), und asynchron wie normal beim Client-Rendering und wird auf dem Client nicht erneut ausgeführt, wenn die Seite gerade war Server-gerendert. Es gibt auch weitere Optionen, die Sie für eine genauere Steuerung der Ausführung pro Komponente angeben können.

Es ist wirklich nützlich zum Laden von Daten - so ziemlich der Anwendungsfall, für den ich ihn geschrieben habe. Versuch es! 😄.

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

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

Denken Sie an all das bedingte Rendering, das Sie mit dem obigen Ansatz nicht machen müssten, weil Sie wissen, dass Sie Ihre Daten haben

Mach mit bei diesem. Ich denke, es ist immer noch ein wesentliches Feature, das React fehlt. Stimmen Sie auch zu, dass selbst wenn Bibliotheken von Drittanbietern dieses Thema behandeln, dies direkt von innen kommen sollte.

Sehr schöne asynchrone Komponentenschnittstelle, @timurtu.

...Hey allerseits, wollte mich hier nur einmischen, da ich verschiedene Dinge habe, die sich sehr direkt auf diesen Thread beziehen, und ich diesen Thread schon lange verfolge:

  • In Anbetracht von SSR denke ich, dass das Abrufen von Daten auf Routenebene am sinnvollsten ist – hier ist der Grund:
    Datenabruf des Redux-First-Routers: Lösung des 80 %-Anwendungsfalls für asynchrone Middleware
  • Server - Rendering mit Redux ist jetzt dumm einfach, hier ist ein Schritt für Schritt Anleitung mit Redux-First - Server-Render wie ein Pro / w Redux-First -
  • Um Code-Splitting richtig zu machen, müssen Sie auch beides machen (dh beides SSR + Splitting) . Es stellte sich heraus, dass beides gleichzeitig zu tun ein großer Schmerzpunkt war , der von wenigen erfolgreich erreicht wurde und bis jetzt noch nie ein allgemeines Paket hatte, um der Community zu helfen. React Universal Component + babel-plugin-universal-import + webpack-flush-chunks (und Extract-css-chunks-webpack-plugin ) sind eine Familie von Paketen, die dieses @timurtu geteilt hat und viele solcher Komponenten, die das Laden von main.js , das Parsen, Auswerten, Rendern und dann schließlich einige Sekunden später das Anfordern der dynamischen Importe erfordern. Dies sind Lösungen, um Flash of Unstyled Content (FOUC) zu lösen, aber nicht um die genauen, auf dem Server gerenderten Chunks in Ihre Seite einzubetten. Universal – der Name meiner Paketfamilie – ermöglicht es Ihnen, dies mit einer viel schnelleren Zeit bis zum Interaktiven (TTI) auf synchrone Weise zu tun, die wie traditionelle Webapps funktioniert. Mehr dazu erfährst du in diesem Artikel:

Ich weiß , ich habe nicht adresse Ebene vs Komponentenebene Vor / Nachteile, aber die erste Verbindung konzentriert sich hauptsächlich auf die. Im Grunde läuft es darauf hinaus, dass die Komponentenebene schlecht für SSR ist, wenn Sie verschachtelte Komponenten haben, deren Daten nacheinander (und nicht parallel) abgerufen werden müssen. Und wenn Sie nur einen Abruf pro Route durchführen müssen, ist es besser, den Vertrag zu formalisieren, indem Sie Ihre Datenabhängigkeiten an eine Routenentität anhängen, anstatt dies in componentDidMount tun . Viele andere oben kamen zu diesem gleichen Schluss. Redux-First Router formalisiert dies sehr schön in Anlehnung an die Art von "Verträgen", die die Arbeit mit Redux so angenehm machen.

Redux-First Router macht viele alte Paradigmen wieder neu, fügt sich aber überraschenderweise als fehlendes Stück in das Redux-Ökosystem ein. Wenn Sie schon immer einen nativen Router für den Workflow von Redux haben wollten, probieren Sie Redux-First Router aus. Es ist relativ neu und ich bin ein Neuling im Bereich Open Source, aber es ist etwas, an dem ich im Grunde ein Jahr lang gearbeitet habe und es hat in den 2 Monaten, in denen es veröffentlicht wurde, viel Anklang gefunden. Die Leute lieben es. Ich hoffe wirklich, dass du es dir ansiehst und es probierst :).

Hier ist auch der Einführungsartikel, der erklärt, wie es für Redux eine weitaus bessere Lösung als React Router ist und wie es die Vision auf eine Weise richtig macht, die der ebenfalls ausgezeichnete Redux-Little Router leider verfehlt hat:
Vorabversion: Redux-First-Router – Ein Schritt über Redux-Little-Router hinaus

Der Hauptunterschied zwischen RLR und RFR besteht darin, dass RFR pro Route einen anderen Aktionstyp (dh URL-Änderung) auslöst, anstatt immer nur LOCATION_CHANGED . Auf diese Weise können Sie URL-Änderungen wie bei allen anderen Aktionen als eindeutige Typen in Ihren Reduzierern umschalten. Es mag klein erscheinen, aber es macht einen großen Unterschied. Aus diesem Grund benötigt es keine <Route /> Komponenten, was im Redux-Land unnötig ist. Es gibt auch eine riesige Liste von Funktionen, die Redux-First Router unterstützt, vom Datenabruf in Route thunks bis hin zu React Native, React Navigation, Code-Splitting, Prefetching und mehr.

Würde gerne die Gedanken der Leute hören.

@faceyspacey danke, ich denke, es react-native-vector-icons ein Symbol als Versprechen zurückgeben. Es ist definitiv eine andere Sichtweise, aber es reduziert componentWillMount oder redux X_GET_START , X_GET_END und X_GET_ERROR Aktionsbausteine, die asynchrone Komponenten an den Zustand binden, wenn Sie diese Anforderung wirklich nur einmal zum Rendern der Komponente stellen möchten. Wenn Sie beispielsweise Daten abfragen, ist es wahrscheinlich sinnvoller, zustandsorientiert zu sein und Redux zu verwenden

@faceyspacey Ich Routenebene sagen, aber wäre es nicht besser, kleinere Komponenten zu haben, die nur darauf angewiesen sind, die benötigten Daten abzurufen, während alles andere um sie herum synchron gerendert wird?

Besser? Wo? Auf React Native? In großen Organisationen wie Facebook in ihren React Native-Apps?

Vielleicht auf React Native. Wenn es SSR gibt, gibt es meiner Meinung nach keine Debatte. Was Sie tun möchten, ist ohne mehrere Abrufe pro Anfrage einfach nicht möglich, es sei denn, Sie beschränken es auf eine Komponente, die den Abruf durchführen kann. An diesem Punkt ist es besser, den Vertrag mit den Routenentitäten zu formalisieren.

Was React Native und SPAs angeht, natürlich. Aber das Testen von Komponenten mit integrierten Data Deps ist mühsam. Apollo hat hier einiges zu bieten, aber zuletzt habe ich nachgesehen, dass sie eine lange To-Do-Liste haben, um das Testen von datengepaarten Komponenten wirklich richtig, dh nahtlos, durchzuführen. Ich bevorzuge ein Setup, bei dem Sie einen Store haben, der einfach einzurichten und zu verspotten ist. Sie können diese Vorgehensweise in allen Ihren Tests wiederverwenden. Von dort aus ist das Testen von Komponenten ein einfaches synchrones Rendering mit Snapshot-Erstellung. Sobald Sie Datendeps in Komponenten hinzufügen, wird das Testen von React weniger intuitiv. Aber die Leute tun es und automatisieren es. Es gibt weniger Standard und ist jetzt näher am benutzerdefinierten Ad-hoc-Code. Es ist produktiver, einen Speicher zu haben, der auf sehr offensichtliche Weise asynchron aufgefüllt werden kann, anstatt potenziell unterschiedliche Strategien für jede asynchrone Komponente zu verwenden, die aufgefüllt werden muss.

Ich bin nicht gegen Komponentenebene, wenn es kein SSR gibt und Sie Ihre Teststrategie nahtlos und aus dem Weg haben. Wenn Sie ein Megakonzern sind, ist dies sinnvoll, da dann nicht jeder Entwickler die oberste Routenebene berühren und möglicherweise für andere brechen muss. Aus diesem Grund hat FB mit Relay und GraphQL auf diesem Weg Pionierarbeit geleistet. Tatsache ist, dass nur 100 Unternehmen der Welt wirklich in diesem Boot sitzen. Daher sehe ich Colocation für die meisten Leute/Apps/Unternehmen eher als Hype denn als Realität an. Sobald Sie die Sache mit der Routenebene im Griff haben und ein Paket haben, das es wie Redux-First Router wirklich gut macht, haben Sie einen weitaus weniger verhedderten, einfacher zu entwickelnden Ansatz für Teams, deren Mitglieder Routenebene berühren dürfen . Schau dir einfach Demo auf der Homepage von Codesandbox an:

https://www.codesandbox.io

Grundsätzlich würde ich gerne Ihre Meinung hören, sobald Sie sehen, wie es gemacht wird. Denken Sie daran, dass dies ein SPA ist. Sie müssen sich also das Demo-Repo oder die Boilerplate für ein SSR-Beispiel

Ich denke, der einfachste Weg ist die Unterstützung der asynchronen Rendermethode (render gibt ein Versprechen zurück). Es wird das Rendern von Root-Komponenten-Warte-Kinder-Komponenten ermöglichen und das Ergebnis des Renderns wird natürlich ein Versprechen sein. Ich habe so etwas in Preact hier implementiert https://github.com/3axap4eHko/preact-async-example

@faceyspacey Danke für die ausführliche Antwort oben. Ich mag die Idee des Redux-First-Routers sehr, er löst definitiv alle meine Probleme und macht die Dinge viel transparenter und sauberer als das, was wir vorher hatten.

Vielen Dank!

@raRaRa freut Routen Level - Daten-fetching bisher gewesen ist. React React v3 hatte Callbacks, und v4 hat ein ausgezeichnetes Muster (insbesondere mit dem react-router-config Paket), aber es ist nicht offensichtlich und in v4 nicht erstklassig. Es ist ein nachträglicher Gedanke. Noch wichtiger ist jedoch, dass es nicht in Redux lebt. Es ist also erst jetzt wirklich das erste Mal, dass Redux-Benutzer eine vollständige "Route-Level"-Abstraktion haben.

Ich denke, mit RFR werden wir jetzt die Bedeutung von "Routenebene" gegenüber "Komponentenebene" überdenken. Dinge wie Relay und Apollo haben einen großen Hype um die Paarung von Datenabhängigkeiten mit Komponenten erzeugt, aber wenn Sie diese 2 nicht verwenden, werden Sie wahrscheinlich irgendwann in einer Welt des Schadens enden, indem Sie Dinge in Lifecycle-Handlern tun .

Das heißt, RFR wird schließlich eine Integration auf Routenebene mit Apollo haben. Es gibt nur sehr wenige Vorteile, Dinge auf Komponentenebene zu tun, abgesehen von den oben genannten seltenen Ausnahmen. Mindestens 80 % der Zeit (und vielleicht 100 % in Ihrer App) können Sie alle benötigten Daten basierend auf der Route ermitteln. Wenn dies nicht möglich ist, liegt es wahrscheinlich daran, dass Sie Daten in Ihrer "Ansichtsebene" ableiten, die sehr gut für alle von der URL interpretiert werden könnten. Selbst mit Tools wie Apollo beginnen Sie also, sich auf problematische Anti-Patterns einzulassen, die Ihnen wahrscheinlich irgendwann Schmerzen bereiten werden, indem Sie "State" nirgendwo anders als auf der Ansichtsebene leben lassen. Dh der Zustand, der zum Durchführen dieser Datenabrufe erforderlich ist. Sie müssen häufig die genauen Parameter verfeinern, die zum Abrufen Ihrer Daten verwendet werden. Sie nehmen also Zustand und Requisiten und transformieren die 2, um die Parameter kurz vor dem Abrufen der Daten zu erhalten. Das ist Zeug, das jetzt in der Ansichtsschicht lebt und seinen Re-Rendering-Mechanismen unterliegt (dh wenn Lebenszyklus-Handler aufgerufen werden). Jede App endet irgendwann damit, dass die Daten nicht abgerufen werden, wenn sie sollten, oder als Reaktion auf empfangene/geänderte nicht verwandte Requisiten abgerufen werden. Und in allen Apps müssen Sie zusätzlichen Code schreiben, um sich vor nicht verwandten Requisiten zu schützen, die sich geändert haben. Dh Requisiten, die sich nicht auf die Parameter auswirken, die zum Abrufen von Daten verwendet werden.

Bei React Native + Apollo ist das Problem geringer, da Sie kein SSR haben und daher möglicherweise sequentielle Datenabrufe auf dem Server stattfinden. Benötigen Sie jedoch wirklich Datenabruf auf Komponentenebene, wenn die Bildschirme so klein sind? Sie wissen in der Regel, was jede „Szene“ an Daten benötigt. Es ist nicht wie ein Desktop-Webpanel-Dashboard mit Millionen von Diagrammen, mehreren Tabellen usw. Dieser eine Anwendungsfall ist vielleicht der wichtigste Ort, an dem Komponentenebene Sinn macht. Aber auch dort können Sie alle Ihre Anforderungen für all diese Widgets an einem Ort angeben und über Apollo abrufen. Ich habe noch nicht genug darüber nachgedacht, aber die Grundidee besteht darin, Ihre GraphQL-Datenanforderungen an eine Route anzuhängen und dann in Ihren Komponenten eine Verbindung zu den Daten wie bei allen anderen Redux-Daten herzustellen. Genauer gesagt besteht die Idee darin, sowohl das connect React-Redux als auch das Äquivalent von Apollo zu eliminieren. Ich will nur einen, und wir wollen, dass er flacher ist. Die Verwendung von Apollo erfordert die Destrukturierung tiefer verschachtelter Daten, um Zugriff auf das zu erhalten, was Sie wirklich in Ihren Komponenten benötigen. Die Vision ist ein Weg, der wie normales Redux aussieht, plus graphql-Spezifikationen auf Routenebene.

Es ist ein bisschen verfrüht, darüber zu sprechen, da ich mich kaum damit befasst habe, aber wenn die Routenebene der richtige Ansatz für Ihre App ist, sollten Apollo + GraphQL keine Ausnahme sein.

Schließlich ist das Testen so viel einfacher, wenn alle Ihre Datenabhängigkeiten und die asynchrone Arbeit von Komponenten getrennt sind. Die Trennung von Bedenken ist ein wichtiger Produktivitätsschub, wenn Sie Ihre Komponenten testen können, ohne sich um das Abrufen von Daten kümmern zu müssen. Natürlich müssen Sie in Ihren Tests Ihren Shop füllen, indem Sie diese asynchronen Abrufe im Voraus durchführen, aber da sie von Ihren Komponenten getrennt sind, können Sie diese Arbeit leicht unter einigen setup Hilfsfunktionen formalisieren, die alle Ihre Tests verwenden, um Füllen Sie den Store auf, bevor Sie testen, wie Komponenten gerendert werden :)

@faceyspacey Ich stimme

Ich bin es sehr gewohnt, Webanwendungen mit dem MVC-Muster zu schreiben, bei dem ich im Grunde alle Daten und Abhängigkeiten auf den Controller ziehe. Dann injiziere ich die Daten in die Ansichten oder die Geschäftslogik. Wie Sie sagten, erleichtert dies das Testen erheblich, da die Ansicht nicht mit dem Abrufen von Daten gekoppelt ist.

Ich habe MVC-Anwendungen gesehen, bei denen der View-Teil Daten abruft und Geschäftslogik ausführt, und das ist schrecklich. Bei beiden habe ich keine Ahnung, welche Daten abgerufen werden, weil sie in einer Ansicht/Komponente versteckt sind und das Testen schwieriger/unmöglich wird.

Für mich ist Redux-First Router sehr ähnlich wie der Controller-Teil in MVC, plus die Routen. Was so viel Sinn macht :)

Danke schön.

Am Ende des Tages ist unser neuer Stack dieser:

M: Redux
V: Reagieren
C: Redux-First Router (wird bald in "Rudy" umbenannt )

Als React herauskam (und insbesondere Redux), schienen wir alle die alte Weisheit von MVC wegwerfen zu wollen, als ob es irgendwie bedeutete, dass wir uns von den Mühen bei der Entwicklung früherer Anwendungen vollständig befreien könnten. Aber ich denke, letztendlich ist es nichts anderes, als einfach noch nicht die Tools zu haben, die es unterstützen. Am Ende des Tages profitieren wir immer noch stark von MVC, selbst bei reaktiven Client-First- Anwendungen.

Es mag im neuen Client-First-Stack im Vergleich zu herkömmlichen serverseitigen MVCs etwas anders funktionieren, aber im Grunde sind es die gleichen drei Aspekte.

Was den Unterschied in Interaktion/Verhalten zwischen den 3 angeht, war es im Wesentlichen so, dass der Controller in der Vergangenheit Modelle in einer Anfangsphase erhielt und dann die Ansichten mit ihnen renderte; und jetzt ist es der Controller (RFR), der sofort eine Ansicht rendert (ausgewählt über ein "UI-Modell", dh Redux-Zustand) und dann die Ansicht ein zweites Mal neu rendert, sobald der Controller Domänenmodelle findet (auch im Redux-Zustand gespeichert).

Hallo Leute, schaut euch diesen Link an. hier ist ein datenserverseitiges Rendering-Beispiel https://github.com/bananaoomarang/isomorphic-redux

@harut55555 idk es mag nur ich sein, aber das scheint eine Menge Code zu sein, um eine Get-Anfrage und eine Post-Anfrage auszuführen

Irgendwelche Veränderungen? Released 16 reagieren und die meisten Lösungen funktionieren nicht

Ich denke, der aktuelle Ansatz besteht darin, Redux mit Tools wie Redux Observable oder Redux Thunk zu verwenden

Mein Anwendungsfall

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

Das Problem ist, dass ich vor dem Rendern der App nicht weiß, welche Art von Komponenten ich haben werde

Warum nicht Daten/Inhalte herunterladen, anstatt eine Komponente nur neugierig zu machen?

Wann? Ich habe dynamische Seiten und die Komponente kann es sein oder nicht

Klingt so, als ob Sie bedingtes Rendering wünschen würden https://reactjs.org/docs/conditional-rendering.html

Ich denke, es ist ein Routing-Problem, reagieren sollte nur die Seite rendern, nicht die Renderdaten verwalten und anfordern

Die Verwendung von @graphql von Apollo macht das Laden von Daten supereinfach, und die komponentenbasierte API von React-Router v4 sorgt für einfaches Composable Routing. Beide sind orthogonal zum Anwendungsstatus in Redux.

Um ein Single-Pass-Streaming-Rendering durchzuführen, das auf Datenabrufe wartet, sollte es render() , ein Promise zurückzugeben (auf dem Client tun Sie es wie jetzt, nur auf dem Server geben Sie ein Promise zurück).

Dann kann @graphql nach dem Laden der Daten einfach ein Promise für render() zurückgeben; der ganze Rest ist einfach React.

Vor einigen Monaten habe ich auf der JSConf Island einen Vortrag gehalten, der die kommenden asynchronen Rendering-Funktionen in React beschreibt (siehe den zweiten Teil): https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react -16.html. Hier geht es um den clientseitigen Datenabruf.

Jetzt hielt @acdlite einen Vortrag darüber, wie die gleichen Konzepte angewendet werden können, um das asynchrone Warten auf Daten auf dem Server in React-Komponenten zu ermöglichen und das Markup nach seiner https://www.youtube.com/watch?v= z-6JC0_cOns

Viel Spaß beim Anschauen dieser Vorträge! Ich denke, dass wir in etwa einem Jahr in der Lage sein werden, dieses Thema abzuschließen und eine offizielle Strategie dafür zu haben.

Ich erinnere mich an Ring. Wir heiraten und planen eine gemeinsame Zukunft. Bitte beende das jetzt und komm zu mir. Zeit arbeitete immer Liebe

Es ist möglich, ReactDOMServer.renderToStaticMarkup zu verwenden, um den Baum zu durchlaufen, ein Lesezeichen dort zu platzieren, wo asynchrone Abhängigkeiten erreicht werden (normalerweise werden Daten von einer API geladen), und den Baum weiter zu durchlaufen, während diese Abhängigkeiten aufgelöst sind, bis der gesamte Baum aufgelöst ist. und dann ein letztes ReactDOMServer.renderToString ausführen, um tatsächlich in HTML zu rendern, was synchron ist, da alle Abhängigkeiten jetzt aufgelöst sind.

Ich habe diesen Ansatz in react-baconjs : render-to-html.js . Es wurde durch einen Kommentar von @gaearon in einer anderen Ausgabe inspiriert, der einen solchen Ansatz empfiehlt.

@steve-taylor ja, das funktioniert. Es ist auch die Problemumgehung, die ich bei React -Frontload verwende , eine allgemeinere Lösung dafür, die in jeder React-App funktioniert.

Wie Sie sagen, fälscht es im Wesentlichen nur das asynchrone Rendering, indem Sie die synchrone Rendering-Logik zweimal ausführen und darauf warten, dass alle Datenladeversprechen, die Sie beim ersten Rendering treffen, aufgelöst werden, bevor Sie das zweite ausführen.

Es funktioniert gut genug, obwohl es offensichtlich etwas verschwenderisch ist (die Ausgabe des ersten Renderings ist mehr als nur Versprechen, es ist auch das eigentliche HTML, das einfach weggeworfen wird). Wird erstaunlich sein, wenn echtes asynchrones Server-Rendering es in React schafft.

@davnicwil schöne Arbeit! Ich habe darüber nachgedacht, die spezifische asynchrone SSR-Lösung zu verallgemeinern. Behandelt Reaction-Frontload eine unbegrenzte asynchrone Tiefe? Fragen, weil Sie „zweimal“ gesagt haben.

@steve-taylor prost! Ja, wenn Sie mit Tiefe die Tiefe der Komponente im Baum meinen, ist sie unbegrenzt. Sie können eine Datenladefunktion für die Komponente selbst (mit einer Komponente höherer Ordnung) deklarieren und dann, wenn dies beim ersten Rendern auftritt, ausführen und das Versprechen gesammelt werden.

Was nicht funktioniert, ist, wenn es weitere untergeordnete Komponenten gibt, die ebenfalls Daten asynchron laden, die dann je nach Ergebnis dynamisch gerendert werden, wenn dies sinnvoll ist. Es bedeutet nur, dass die App so strukturiert sein muss, dass es diese Art der Verschachtelung nicht gibt, die meiner Meinung nach in der Praxis keine wirkliche Einschränkung darstellt. Tatsächlich ist es in vielerlei Hinsicht UX-technisch besser, da verschachtelte asynchrone Logik natürlich serielle Anfragen und längere Wartezeiten bedeutet, während Flattening parallele Anfragen bedeutet.

Das heißt, das Verschachtelungsproblem (in der Tat vielleicht dieses ganze asynchrone Server-Rendering-Problem) könnte mit dem Suspense-Zeug, das in naher Zukunft in React kommt, gelöst werden. 🤞.

Zu Ihrer Information, wir haben begonnen, daran zu arbeiten.

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

Super @gaearon!

@davnicwil react-baconjs unterstützt unbegrenzte Tiefe.

@gaearon Ich frage mich, ob dies eine Unterstützung für Observables im

Ich würde diese Funktion gerne aus zwei Gründen haben. Zunächst wird in meiner App der Zustand in einem Webworker gespeichert. Während das Abrufen von Bits dieses Zustands, die von der Komponente benötigt werden, ein asynchroner Vorgang ist, dauert es jedoch etwa 5 ms und es macht keinen Sinn, dass React etwas rendert, während es auf die Daten wartet. Zweitens gibt es im Moment keine universelle Möglichkeit, ein Observable in eine Komponente umzuwandeln: Wenn Ihr Observable den ersten Wert synchron ausgibt, abonnieren Sie, um diesen Wert zu erhalten, dann abmelden, dann erneut abonnieren, um auf Änderungen zu warten (das ist das Replay Themenbeispiel in Create-Observable-Dokumenten). Und wenn der erste Wert asynchron ausgegeben wird, rendern Sie zunächst null und warten auf Änderungen.

@ steve-taylor -Frontloading reagieren nun auch unbegrenzte Tiefe der Komponente nisten, mit dem 1.0.7 Release.

Ich hoffe, diese Bibliothek ist für einige Leute nützlich, die in diesem Thread landen - wenn Sie nach einer asynchronen Datenladelösung suchen, die mit Client / Server-Rendering mit sehr minimalem Integrationsaufwand funktioniert, sollten Sie sie sich ansehen.

Wir waren auf dieses Problem schon einmal gestoßen, als unsere App mit React Hooks erstellt wurde, und dann haben wir ein Paket " react-use-api" erstellt , um es zu lösen, ein benutzerdefinierter Hook, der API-Daten abruft und SSR unterstützt. Ich hoffe, dass das Paket Menschen in Not helfen kann.

Wir freuen uns jedoch immer noch darauf, auf eine offizielle Lösung zu warten. Genau wie React-Frontload sagt, gibt es keine integrierte Möglichkeit, auf das asynchrone Laden von Daten zu warten, sobald das Rendern beginnt.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen