React: Implementieren Sie das seitliche Laden von Daten

Erstellt am 13. März 2015  ·  136Kommentare  ·  Quelle: facebook/react

Dies ist eine erstklassige API für das seitliche Laden von zustandslosen (obwohl möglicherweise gespeicherten) Daten aus einem globalen Speicher/Netzwerk/einer globalen Ressource, wobei möglicherweise Props/Status als Eingabe verwendet werden.

type RecordOfObservables = { [key:string]: Observable<mixed> };

class Foo {

  observe(): RecordOfObservables {
    return {
      myContent: xhr(this.props.url)
    };
  }

  render() {
    var myContent : ?string = this.data.myContent;
    return <div>{myContent}</div>;
  }

}

Observe() wird nach componentWillMount/componentWillUpdate, aber vor dem Rendern ausgeführt.

Für jeden Schlüssel/Wert im Datensatz. Abonnieren Sie das Observable im Wert.

subscription = observable.subscribe({ onNext: handleNext });

Wir erlauben, dass onNext synchron von Subscribe aufgerufen wird. Wenn ja, setzen wir:

this.data[key] = nextValue;

Andernfalls lassen wir es für das anfängliche Rendern undefiniert. (Vielleicht setzen wir es auf null?)

Dann wird wie gewohnt gerendert.

Jedes Mal, wenn onNext aufgerufen wird, planen wir ein neues "this.data[key]", das effektiv ein erzwungenes Update für diese Komponente auslöst. Wenn dies die einzige Änderung ist, wird die Beobachtung nicht erneut ausgeführt (componentWillUpdate -> render -> componentDidUpdate).

Wenn sich Props/State geändert hat (dh eine Aktualisierung von recieveProps oder setState), dann wird Observe() erneut ausgeführt (während der Abstimmung).

An diesem Punkt durchlaufen wir den neuen Datensatz und abonnieren alle neuen Observables.

Danach kündigen Sie die vorherigen Observables.

subscription.dispose();

Diese Reihenfolge ist wichtig, da sie es dem Anbieter von Daten ermöglicht, eine Referenzzählung seines Caches durchzuführen. Das heißt, ich kann Daten zwischenspeichern, solange niemand darauf hört. Wenn ich mich sofort abmeldete, würde der Referenzzähler auf Null sinken, bevor ich dieselben Daten erneut abonniere.

Wenn eine Komponente ausgehängt wird, kündigen wir automatisch alle aktiven Abonnements.

Wenn das neue Abonnement nicht sofort onNext aufgerufen hat, verwenden wir weiterhin den vorherigen Wert.

Wenn sich also mein this.props.url aus meinem Beispiel ändert und ich eine neue URL abonniere, zeigt myContent weiterhin den Inhalt der vorherigen URL an, bis die nächste URL vollständig geladen ist.

Dies hat dieselbe Semantik wie das Tag <img /> . Wir haben gesehen, dass dies zwar verwirrend sein und zu Inkonsistenzen führen kann, aber ein ziemlich vernünftiger Standard ist, und es ist einfacher, einen Spinner anzuzeigen, als den entgegengesetzten Standard zu haben.

Es empfiehlt sich möglicherweise, sofort einen „Null“-Wert zu senden, wenn Sie die Daten nicht zwischengespeichert haben. Eine andere Alternative besteht darin, dass ein Observable sowohl die URL (oder ID) als auch den Inhalt im Ergebnis bereitstellt.

class Foo {

  observe() {
    return {
      user: loadUser(this.props.userID)
    };
  }

  render() {
    if (this.data.user.id !== this.props.userID) {
      // Ensure that we never show inconsistent userID / user.name combinations.
      return <Spinner />;
    }
    return <div>Hello, {this.data.user.name} [{this.props.userID}]!</div>;
  }

}

Wir sollten den RxJS-Vertrag von Observable verwenden, da dieser häufiger verwendet wird und eine synchrone Ausführung ermöglicht, aber sobald der Vorschlag von @jhusain häufiger verwendet wird, wechseln wir stattdessen zu diesem Vertrag.

var subscription = observable.subscribe({ onNext, onError, onCompleted });
subscription.dispose();

Wir können bei Bedarf weitere Lebenszyklus-Hooks hinzufügen, die auf diese Ereignisse reagieren.

Hinweis: Dieses Konzept ermöglicht es, dass sich Seitwärtsdaten wie "Verhalten" verhalten - genau wie Requisiten. Das bedeutet, dass wir den Begriff Zustand für diese Dinge nicht überladen müssen. Es ermöglicht Optimierungen wie das Wegwerfen der Daten, nur um sie später erneut zu abonnieren. Es ist wiederherstellbar.

Component API Big Picture

Hilfreichster Kommentar

Wenn jemand mit dieser Art von API herumspielen möchte, ich habe ein wirklich dummes Polyfill für observe als Komponente höherer Ordnung erstellt:

import React, { Component } from 'react';

export default function polyfillObserve(ComposedComponent, observe) {
  const Enhancer = class extends Component {
    constructor(props, context) {
      super(props, context);

      this.subscriptions = {};
      this.state = { data: {} };

      this.resubscribe(props, context);
    }

    componentWillReceiveProps(props, context) {
      this.resubscribe(props, context);
    }

    componentWillUnmount() {
      this.unsubscribe();
    }

    resubscribe(props, context) {
      const newObservables = observe(props, context);
      const newSubscriptions = {};

      for (let key in newObservables) {
        newSubscriptions[key] = newObservables[key].subscribe({
          onNext: (value) => {
            this.state.data[key] = value;
            this.setState({ data: this.state.data });
          },
          onError: () => {},
          onCompleted: () => {}
        });
      }

      this.unsubscribe();
      this.subscriptions = newSubscriptions;
    }

    unsubscribe() {
      for (let key in this.subscriptions) {
        if (this.subscriptions.hasOwnProperty(key)) {
          this.subscriptions[key].dispose();
        }
      }

      this.subscriptions = {};
    }

    render() {
      return <ComposedComponent {...this.props} data={this.state.data} />;
    }
  };

  Enhancer.propTypes = ComposedComponent.propTypes;
  Enhancer.contextTypes = ComposedComponent.contextTypes;

  return Enhancer;
}

Verwendung:

// can't put this on component but this is good enough for playing
function observe(props, context) {
  return {
    yourStuff: observeYourStuff(props)
  };
}

class YourComponent extends Component {
  render() {
    // Note: this.props.data, not this.data
    return <div>{this.props.data.yourStuff}</div>;
  }
}

export default polyfillObserve(YourComponent, observe);

Alle 136 Kommentare

undefined ist wahrscheinlich der sicherste Wert, der data zugewiesen werden kann, bis das Observable seinen ersten Wert über onNext liefert. Zum Beispiel weisen wir in Relay null (Daten nicht vorhanden) und undefined (noch nicht abgerufen) unterschiedliche Bedeutungen zu, sodass unser idealer Standarddatenwert undefined . Die Alternative besteht darin, eine neue Methode bereitzustellen, z. B. getInitialData , aber ich vermute, dass dies unnötig/übertrieben ist.

Das ist ziemlich interessant, aber aus Sicht der statischen Typisierung bin ich nicht so glücklich mit dem Schlüssel/Wert-System, ihr Typ ist so gut wie unmöglich auszudrücken.
Warum sollte observe ein einzelnes Observable zurückgeben und den aufgelösten Wert auf this.data setzen/zusammenführen:

class Foo {

  observe() {
    return (
      loadUser(this.props.userID)
        .map(user => { user })
  }

  render() {
    if (this.data.user.id !== this.props.userID) {
      // Ensure that we never show inconsistent userID / user.name combinations.
      return <Spinner />;
    }
    return <div>Hello, {this.data.user.name} [{this.props.userID}]!</div>;
  }

}

Und für den Fall von Mehrfachabruf so etwas wie:

class Foo {

  observe() {
    return (
     combineLatest(
      loadUser(this.props.userID),
      loadSomethingElse(this.props.somethingElseId),
      (user, somethingElse) => ({ user, somethingElse})
     )
  }

  render() {
    ..
  }

}

Dies ist vielleicht etwas ausführlicher, ermöglicht aber einen schönen statischen Typ:

interface Comp<T> {
  observe(): Observable<T>;
  data: T;
}

Anstatt observe erneut auszuführen, wenn sich Requisiten/Status ändern, können wir auch auf 'Props' 'State' als Observable zugreifen:

class Foo {

  observe(propsStream) {
    return (
      propsStream
        .flatMap(({ userID }) => loadUser(userId))
        .map(user => { user })
    );
  }

  render() {
    if (this.data.user.id !== this.props.userID) {
      // Ensure that we never show inconsistent userID / user.name combinations.
      return <Spinner />;
    }
    return <div>Hello, {this.data.user.name} [{this.props.userID}]!</div>;
  }
}

Der Grund dafür ist, dass wir nicht die Verwendung von Kombinatoren und das Verständnis von RxJS erfordern wollen, um (mehrere) Observables abonnieren zu können. Zwei Observables auf diese Weise zu kombinieren, ist ziemlich verwirrend. Tatsächlich werden wir zumindest für unsere Datenquellen wahrscheinlich die Abonnement-API implementieren, aber nicht einmal die Kombinatoren in den Observables-Prototyp aufnehmen. Das ist keine Voraussetzung, aber Sie können bei Bedarf Kombinatoren verwenden.

Um jedoch einen einfachen Flux-Shop zu abonnieren, sollten Sie dies nicht tun müssen.

Ich denke, dass Flow wahrscheinlich in der Lage sein wird, diesen statischen Typ mithilfe von Einschränkungen zu handhaben, aber ich werde mich mit diesen Leuten erkundigen, um sicherzugehen. Ich denke, dass es ausreichen wird, die Dateneigenschaft einzugeben, und dann kann der Beobachtungstyp impliziert werden.

class Foo extends React.Component {
  data : { user : User, content : string };
  observe() /* implied as { user : Observable<User>, content : Observable<string> } */ {
  }
}

Bei dieser Änderung geht es nicht darum, Observables als Möglichkeit zur Beschreibung des Anwendungsstatus zu verwenden. Das kann darüber hinaus implementiert werden, wie Sie es zuvor getan haben. Hier geht es ausdrücklich nicht um den Anwendungsstatus, da diese Methode idempotent ist. Das Framework kann bei Bedarf kostenlos abbestellt und erneut abonniert werden.

cc @ericvicenti

Zumindest im Fall von Typoskript gäbe es keine Möglichkeit, den Rückgabetyp von observe basierend auf dem Typ von data , zumindest bis so etwas wie https://github.com/ Microsoft/TypeScript/issues/1295 ist implementiert.

Ich würde das gerne für die nächste Version von React DnD verwenden, aber das erfordert natürlich das Warten auf React 0.14.
Ich frage mich, ob ich dies vorerst mit einer Komponente höherer Ordnung „polyfill“ kann, die this.data auf eine ref -Instanz setzt. Könnte aber zu verrückt sein.

Wäre es möglich, Promises einzuhalten? Dann könnte man einen Promises-Baum verwenden, um Daten für den gesamten Komponentenbaum vor dem ersten Rendern aufzulösen! Dies wäre sehr nützlich für serverseitiges React.

Was sind die Vorteile davon, dies zu einer erstklassigen API zu machen? Dies könnte im Wesentlichen unter Verwendung einer "Komponente höherer Ordnung" erreicht werden.

Was sind die Vorteile davon, dies zu einer erstklassigen API zu machen? Dies könnte im Wesentlichen unter Verwendung einer "Komponente höherer Ordnung" erreicht werden.

Das Einpacken von 5 HOCs, um 5 Abonnements zu erhalten, ist etwas unhandlich und für Anfänger schwerer zu verstehen. Das Verständnis componentWillReceiveProps ist ebenfalls nicht trivial. Das behebt beides.

Ich für meinen Teil heiße unsere neuen beobachtbaren Oberherren willkommen.

Ich frage mich, ob dies dazu beitragen kann, https://github.com/chenglou/react-state-stream näher an die Vanilla-API von React heranzuführen

Würde es nicht nur einen HOC brauchen? In dem Beispiel in Ihrem Medium-Beitrag iterieren Sie über stores und abonnieren jedes.

stores.forEach(store =>
  store.addChangeListener(this.handleStoresChanged)
);

@aaronshaf Hängt sicher vom Anwendungsfall ab. Manchmal sind es verschiedene Arten von staatlichen Quellen, nicht nur „mehrere Geschäfte“. Aber ich kann nicht im Namen des React-Teams sagen, lass uns hören, was @sebmarkbage sagt.

Würde jetzt gerne eine Art Füllwatte haben, um damit zu spielen. Ich bin noch nicht ganz auf die Idee gekommen. Was ist der Mechanismus, der mit dem Umgang mit zukünftigen Updates verbunden ist? Ich werde noch etwas Zeit damit verbringen, es zu verstehen. Ich denke, es sollte mit einem einfachen Mixin machbar sein.

( @vjeux hat mir gesagt, ich soll mich einschalten! also hier bin ich.)

Ich möchte nicht meine eigene Arbeit fördern, aber ich denke, dass dieser Hook dem Hook getNexusBindings in React Nexus sehr ähnlich ist. Sie deklarieren Datendeps auf Komponentenebene über einen Lebenszyklus-Hook (der von Requisiten abhängen kann).

Die API sieht so aus:

class UserDetails {
  getNexusBindings(props) {
    return {
      // binding to data in the datacenter
      posts: [this.getNexus().remote, `users/${this.props.userId}/posts`],
      // binding to data in the local flux
      mySession: [this.getNexus().local, `session`],
    }
  }
}

Die Bindung wird während componentDidMount und componentWillReceiveProps angewendet/aktualisiert. Im letzteren Fall werden die nächsten Bindungen mit den vorherigen Bindungen unterschieden; Entfernte Bindungen werden abgemeldet, hinzugefügte Bindungen werden abonniert. Der zugrunde liegende Abruf-/Aktualisierungsmechanismus wird in der Implementierung von Nexus Flux beschrieben. Grundsätzlich können Sie mit derselben API entweder lokale Daten (traditionelle lokale Stores) oder Remote-Daten (mit GET abrufen und Patches über Websockets/Polyfill erhalten) abonnieren. Sie könnten tatsächlich Daten aus einem anderen Fenster (mit postWindow) oder einem WebWorker/ServiceWorker abonnieren, aber ich habe noch keinen wirklich nützlichen Anwendungsfall dafür gefunden.

Um es kurz zu machen, Sie beschreiben synchron Datendeps auf Komponentenebene mit einer Flux-Abstraktion und die Hooks stellen sicher, dass Ihre Abhängigkeiten automatisch abonniert, bei Updates eingefügt und abgemeldet werden.

Aber es kommt auch mit einer netten Funktion: Die exakt gleichen Lebenszyklusfunktionen werden genutzt, um einen Datenvorabruf zum Zeitpunkt des serverseitigen Renderns durchzuführen. Grundsätzlich ruft React Nexus ausgehend von der Wurzel und rekursiv von dort aus die Bindungen vorab ab, rendert die Komponente und fährt mit den Nachkommen fort, bis alle Komponenten gerendert sind.

@aaronshaf @gaearon Der Vorteil, es erstklassig zu machen, ist:

1) Es nagt nicht am Requisiten-Namensraum. ZB muss die Komponente höherer Ordnung keinen Namen wie data von Ihrem Props-Objekt beanspruchen, der für nichts anderes verwendet werden kann. Das Verketten mehrerer Komponenten höherer Ordnung verschlingt immer mehr Namen, und jetzt müssen Sie einen Weg finden, diese Namen eindeutig zu halten. Was ist, wenn Sie etwas komponieren, das möglicherweise bereits komponiert wurde, und jetzt einen Namenskonflikt haben?

Außerdem denke ich, dass die beste Vorgehensweise für Komponenten höherer Ordnung darin bestehen sollte, den Vertrag der umhüllten Komponente nicht zu ändern. Dh konzeptionell sollten die gleichen Requisiten rein wie raus sein. Andernfalls ist es verwirrend, zu verwenden und zu debuggen, wenn der Verbraucher einen völlig anderen Satz von Requisiten liefert, als er erhält.

2) Wir müssen nicht state verwenden, um den letzten Wert zu speichern. Das data -Konzept ähnelt props in dem Sinne, dass es sich um eine reine Memoisierung handelt. Es steht uns frei, es jederzeit wegzuwerfen, wenn wir die Erinnerung zurückfordern müssen. Beispielsweise könnten wir in einem unendlichen Scroll automatisch unsichtbare Teilbäume bereinigen.

@RickWong Ja, es wäre ziemlich trivial, Promises zu unterstützen, da sie eine Teilmenge von Observables sind. Wir sollten das wahrscheinlich tun, um unparteiisch zu sein. Ich würde jedoch wahrscheinlich immer noch davon abraten, sie zu verwenden. Ich finde, dass sie Observables aus folgenden Gründen unterlegen sind:

A) Sie können nicht automatisch vom Framework abgebrochen werden. Das Beste, was wir tun können, ist, eine verspätete Lösung zu ignorieren. In der Zwischenzeit hält das Versprechen an möglicherweise teuren Ressourcen fest. Es ist leicht, in eine unangenehme Situation von Abonnieren / Abbrechen / Abonnieren / Abbrechen ... von lang laufenden Timern / Netzwerkanfragen zu geraten, und wenn Sie Promises verwenden, werden sie nicht an der Wurzel abgebrochen, und daher müssen Sie nur auf die warten Ressourcen zum Abschließen oder Zeitüberschreitung. Dies kann sich nachteilig auf die Leistung großer Desktop-Seiten (wie facebook.com) oder latenzkritischer Apps in speicherbeschränkten Umgebungen (wie reaktionsnativ) auswirken.

B) Sie sperren sich darauf ein, nur einen einzigen Wert zu erhalten. Wenn sich diese Daten im Laufe der Zeit ändern, können Sie Ihre Ansichten nicht ungültig machen und landen in einem inkonsistenten Zustand. Es ist nicht reaktiv. Für ein einzelnes serverseitiges Rendering könnte das in Ordnung sein, aber auf dem Client sollten Sie es idealerweise so gestalten, dass Sie neue Daten an die Benutzeroberfläche streamen und automatisch aktualisieren können, um veraltete Daten zu vermeiden.

Daher finde ich, dass Observable die überlegene API zum Erstellen ist, da es Sie nicht daran hindert, diese Probleme bei Bedarf zu beheben.

@elierotenberg Danke fürs Mitmachen! Es scheint tatsächlich sehr ähnlich zu sein. Gleiche Vorteile. Sehen Sie Einschränkungen bei meinem Vorschlag? Dh da fehlt etwas, das React Nexus hat, worauf man nicht aufbauen konnte? Wäre schön, wenn wir uns nicht aus wichtigen Anwendungsfällen ausschließen würden. :)

Aus Sicht des Server-Renderings ist es wichtig, dass wir in der Lage sind, den endgültigen renderToString zu verschieben, bis das Observable/Promise mit Daten aufgelöst wurde, die asynchron abgerufen werden könnten. Andernfalls sind wir immer noch in der Position, alle asynchronen Datenabrufe außerhalb von React durchführen zu müssen, ohne zu wissen, welche Komponenten bereits auf der Seite sein werden.

Ich glaube, dass der React-Nexus das asynchrone Laden innerhalb einer Komponente zulässt, bevor der Renderbaum fortgesetzt wird.

Ja, React-Nexus trennt ausdrücklich:
1) verbindliche Deklaration als getNexusBindings (was eine synchrone, nebenwirkungsfreie Lebenszyklusmethode ist, ähnlich wie rendern - eigentlich hieß es früher renderDependencies, aber ich fand es verwirrend),
2) Abonnement/Aktualisierung der Bindung als applyNexusBindings (das synchron ist und die vorherigen Nexus-Bindungen unterscheidet, um zu bestimmen, welche neuen Bindungen abonniert und welche abgemeldet werden müssen)
3) Binden des Vorablesezugriffs als prefetchNexusBindings (was asynchron ist und aufgelöst wird, wenn der "Anfangswert" (was auch immer das bedeutet) bereit ist)

ReactNexus.prefetchApp(ReactElement) gibt ein Promise(String html, Object serializableData) zurück. Dieser Hook imitiert die Konstruktion des React-Baums (unter Verwendung instantiateReactComponent ) und konstruiert/vorabruft/gerendert die Komponenten rekursiv. Wenn der gesamte Komponentenbaum „bereit“ ist, ruft er schließlich React.renderToString , da er weiß, dass alle Daten bereit sind (Modulo-Fehler). Nach der Lösung kann der Wert dieses Versprechens in die Serverantwort eingefügt werden. Auf dem Client funktioniert der reguläre React.render() -Lebenszyklus wie gewohnt.

Wenn jemand mit dieser Art von API herumspielen möchte, ich habe ein wirklich dummes Polyfill für observe als Komponente höherer Ordnung erstellt:

import React, { Component } from 'react';

export default function polyfillObserve(ComposedComponent, observe) {
  const Enhancer = class extends Component {
    constructor(props, context) {
      super(props, context);

      this.subscriptions = {};
      this.state = { data: {} };

      this.resubscribe(props, context);
    }

    componentWillReceiveProps(props, context) {
      this.resubscribe(props, context);
    }

    componentWillUnmount() {
      this.unsubscribe();
    }

    resubscribe(props, context) {
      const newObservables = observe(props, context);
      const newSubscriptions = {};

      for (let key in newObservables) {
        newSubscriptions[key] = newObservables[key].subscribe({
          onNext: (value) => {
            this.state.data[key] = value;
            this.setState({ data: this.state.data });
          },
          onError: () => {},
          onCompleted: () => {}
        });
      }

      this.unsubscribe();
      this.subscriptions = newSubscriptions;
    }

    unsubscribe() {
      for (let key in this.subscriptions) {
        if (this.subscriptions.hasOwnProperty(key)) {
          this.subscriptions[key].dispose();
        }
      }

      this.subscriptions = {};
    }

    render() {
      return <ComposedComponent {...this.props} data={this.state.data} />;
    }
  };

  Enhancer.propTypes = ComposedComponent.propTypes;
  Enhancer.contextTypes = ComposedComponent.contextTypes;

  return Enhancer;
}

Verwendung:

// can't put this on component but this is good enough for playing
function observe(props, context) {
  return {
    yourStuff: observeYourStuff(props)
  };
}

class YourComponent extends Component {
  render() {
    // Note: this.props.data, not this.data
    return <div>{this.props.data.yourStuff}</div>;
  }
}

export default polyfillObserve(YourComponent, observe);

Ist Observable eine konkrete, vereinbarte Sache neben Bibliotheksimplementierungen? Was ist der Vertrag, ist er einfach genug zu implementieren, ohne Speck oder Rxjs verwenden zu müssen? So schön eine erstklassige API für das Seitenladen von Daten auch wäre, es scheint seltsam für React, eine API hinzuzufügen, die auf einem nicht spezifizierten/sehr initial spezifizierenden Primitiv basiert, angesichts der stetigen Bewegung von React in Richtung einfaches js. Würde uns so etwas an eine bestimmte Benutzerlandimplementierung binden?

Nebenbei, warum nicht Streams? Ich habe kein Pferd im Rennen, aber ich frage mich ehrlich; Es wurde bereits an Webstreams gearbeitet, und natürlich gibt es node

@jquense Es wird aktiv an einem Vorschlag gearbeitet, Observable zu ECMAScript 7(+) hinzuzufügen, also würde dies idealerweise zu einfachem JS werden. https://github.com/jhusain/asyncgenerator (derzeit veraltet.)

Eine Abhängigkeit von RxJS würden wir nicht eingehen. Die API ist trivial selbst zu implementieren, ohne RxJS zu verwenden. RxJS kommt dem aktiven ECMAScript-Vorschlag am nächsten.

most.js scheint auch machbar zu sein.

Die API von Bacon.js scheint schwierig zu nutzen, ohne eine Abhängigkeit von Bacon einzugehen, da die Typen Bacon.Event zum Trennen von Werten verwendet werden.

Die Stream-APIs sind zu hoch und weit von diesem Anwendungsfall entfernt.

Gibt es eine Art "Vor dem Rendern warten"-Option? Ich meine, auf dem Client ist es nicht notwendig, vor dem Rendern auf alle Observables zu warten, aber auf dem Server möchten Sie warten, bis sie aufgelöst sind, damit das render() jeder Komponente ist voll , nicht teilweise.

[parent] await observe(). full render(). -> [foreach child] await observe(). full render().

Bei all meinen Untersuchungen habe ich festgestellt, dass dies der wichtigste Lifecycle-Hook ist, der in serverseitigem React fehlt.

Im Anschluss an diese Diskussion habe ich versucht, im folgenden Beitrag zusammenzufassen, was React Nexus tut:

Ismorphe Apps richtig gemacht mit React Nexus

Hier ist das Diagramm der Kern-Prefetching-Routine:

React Nexus

Eine Abhängigkeit von RxJS würden wir nicht eingehen. Die API ist trivial selbst zu implementieren, ohne RxJS zu verwenden. RxJS kommt dem aktiven ECMAScript-Vorschlag am nächsten.

:+1: Das ist die große Sorge für mich, wenn ich beispielsweise an Versprechungen denke, bei denen die Umsetzung Ihrer eigenen äußerst belastend ist, wenn Sie nicht wissen, was Sie tun. Ich denke, sonst haben Sie eine implizite Anforderung an eine bestimmte Bibliothek im Ökosystem. Am Rande ... eines der netten Dinge aus der Promise-Welt ist die A+-Testsuite, also gab es sogar über Bibliotheken hinweg zumindest eine Zusicherung einer gemeinsamen Funktionalität von .then , was für Promise-Interop hilfreich war, bevor sie es waren standardisiert.

Das ist die große Sorge für mich, wenn ich beispielsweise an Versprechungen denke, bei denen die Umsetzung Ihrer eigenen äußerst belastend ist, wenn Sie nicht wissen, was Sie tun. Ich denke, sonst haben Sie eine implizite Anforderung an eine bestimmte Bibliothek im Ökosystem.

Völlig einverstanden. Zum Glück haben Observables einen wirklich einfachen Vertrag und nicht einmal eingebaute Methoden wie then also sind sie in gewisser Weise sogar einfacher als Versprechungen.

Sie könnten komplizierter (und langsamer) werden, wenn das Komitee darauf besteht, dass das Aufrufen von next eine Mikroaufgabe wie Promises plant.

Das würde einige stören, da viele Muster darauf beruhen, dass onNext in RxJS synchron ist :/

Ich denke, ein gängiges Flux-Store-Muster könnte darin bestehen, eine Karte der Observables pro Schlüssel zu führen, damit sie wiederverwendet werden können. Bereinigen Sie sie dann, wenn alle abgemeldet sind.

Auf diese Weise können Sie Dinge tun wie: MyStore.get(this.props.someID) und erhalten immer dasselbe Observable zurück.

Auf diese Weise können Sie Dinge tun wie: MyStore.get(this.props.someID) und erhalten immer dasselbe Observable zurück.

Würde die Verwendung this.props.key (vergangen, ich weiß) Sinn machen? In den meisten Fällen übergeben Sie eine solche eindeutige Kennung bereits mit <... key={child.id} .. /> .

Auf diese Weise können Sie Dinge tun wie: MyStore.get(this.props.someID) und erhalten immer dasselbe Observable zurück.

Das ist das Muster, das ich auch für React Nexus verwende. Store#observe gibt einen gespeicherten, unveränderlichen Beobachter zurück; Es wird bereinigt (einschließlich relevanter Backend-spezifischer Bereinigungsmechanismen, wie z. B. das Senden einer tatsächlichen "Abmelden"-Nachricht oder was auch immer), wenn alle Abonnenten für mindestens einen Tick weg sind.

@sebmarkbage @gaearon Wie würde die Arbeit auf dem Server in v0.14 beobachtet werden?
Wäre es in der Lage, richtig zu warten, bis alle Beobachter aufgelöst sind, bevor es ähnlich wie React-Nexus in String gerendert wird (aber eingebaut, um zu reagieren)?

IMO wäre es großartig, wenn Komponenten auf den ersten beobachteten Wert warten würden, bevor sie zum Rendern auf dem Server „bereit“ wären.

@gaearon : IMO wäre es großartig, wenn Komponenten auf den ersten beobachteten Wert warten würden, bevor sie zum Rendern auf dem Server „bereit“ wären.

Ja, :+1: für asynchrones Rendern. In der Zwischenzeit ist react-async von @andreypopp eine Alternative, aber es erfordert fibers , um React zu "hacken". Es wäre großartig, wenn React asynchrones Rendering out-of-the-box unterstützen könnte.

Asynchrones Rendern ist etwas, das wir gerne unterstützen würden, aber es ist nicht Teil dieses Problems. L

Wird es leider nicht in 0.14 schaffen. Viele verschiedene Designs müssen berücksichtigt und umgestaltet werden.

Fühlen Sie sich frei, eine Beschreibung der architektonischen Änderungen an den Interna zu erstellen und herauszugeben, die erforderlich sind, um dies zu erreichen.

Ich hatte den gleichen Gedanken wie @gaearon bezüglich : -streaming-state . Könnte es in Anbetracht all der möglichen Anwendungen außer dem Seitenladen einen besseren Namen als data ? Beispielsweise würde observed es eindeutiger mit der Methode in Verbindung bringen.

Ich wollte nicht mit Bikeshedding entgleisen, sondern wollte das hier rauswerfen.

Ich kann die Observables in React kaum erwarten. Dies sollte React reaktiv machen, wie ich es verstehe

Ich experimentiere mit einer ähnlichen Idee, während ich react-async neu schreibe, siehe README .

Der bemerkenswerte Unterschied besteht darin, dass ich eine explizite beobachtbare/Prozessidentität einführe, um Prozesse in Einklang zu bringen, ähnlich wie es React mit key -Prop und zustandsbehafteten Komponenten macht.

Wenn sich id des benannten Prozesses ändert, stoppt React Async die alte Prozessinstanz und startet eine neue.

Die API sieht so aus:

import React from 'react';
import Async from 'react-async';

function defineFetchProcess(url) {
  return {
    id: url,
    start() {
      return fetch(url)
    }
  }
}

function MyComponentProcesses(props) {
  return {
    user: defineFetchProcess(`/api/user?user${props.userID}`)
  }
}

@Async(MyComponentProcesses)
class MyComponent extends React.Component {

  render() {
    let {user} = this.props
    ...
  }
}

Die Prozess-API folgt jetzt syntaktisch und namentlich der ES6 Promises API, aber semantisch wird nicht erwartet, dass process.then(onNext, onError) nur einmal pro Prozess live aufgerufen wird. Es wurde entwickelt, um den beliebtesten (?) Anwendungsfall des Abrufens von Daten über Versprechungen zu berücksichtigen. Aber um ehrlich zu sein, denke ich, dass ich es jetzt ändern muss, um Verwirrung zu vermeiden.

Ich kann mich irren, aber ich denke, das einzige, was die Implementierung der vorgeschlagenen (in dieser Ausgabe) API im Userland verhindert, ist das Fehlen des Lebenszyklus-Hooks, der kurz vor dem Rendern wie componentWillUpdate , aber mit neuem props ausgeführt wird und state bereits auf der Instanz installiert.

Eine Sache, die noch nicht besprochen wurde, ist die Handhabung des onError Callbacks. Wenn ein Observable einen Fehler erzeugt, sollten diese Informationen für die Komponente irgendwie verfügbar sein. Da React den eigentlichen subscribe(callbacks) -Aufruf verarbeitet, benötigen wir eine standardisierte Methode, um es in dieses Callback-Objekt einzufügen.

Um Anwendungsentwicklern die größtmögliche Flexibilität zu bieten, sehe ich zwei Ansätze. Die erste besteht darin, die Fehler in einem Attribut der obersten Ebene zu platzieren, ähnlich wie bei this.data . Dies scheint unglaublich schwerfällig zu sein und frisst den Namensraum der Komponente weiter auf.
Die zweite würde es Entwicklern ermöglichen, ihren eigenen onError -Callback als lebenszyklusähnliche Funktion zu definieren. Wenn ich eine benutzerdefinierte Fehlerbehandlung für meine Observables haben möchte, könnte ich so etwas wie hinzufügen

onObserveError(key, error) {
  // do something with the error
  this.state.errors[key] = error;
  this.setState({ errors: this.state.errors });
}

Dies ähnelt dem, was wir in der kommenden Iteration von Parse+React getan haben. Wir haben unsere eigene Fehlerbehandlung entwickelt, um die gewünschte API zu erstellen. Fehler werden einer privaten { name => error } Map hinzugefügt, und Komponenten haben eine öffentliche Methode der obersten Ebene, queryErrors() , die einen Klon der Map zurückgibt, wenn sie nicht leer ist, und null andernfalls (unter Berücksichtigung eines einfachen if (this.queryErrors()) .

Natürlich ist auch das Definieren neuer reservierter Methoden eine knifflige Angelegenheit; Ich werde nicht behaupten, dass es nicht so ist. Aber wir brauchen eine Möglichkeit, der Komponente beim Rendern implizit oder explizit Fehler zur Verfügung zu stellen.

@andrewimm Die Idee ist, dies in ein generisches Fehlerfortpflanzungssystem einzuspeisen, das Fehler in der Hierarchie nach oben bläst, bis sie von einer Fehlergrenze behandelt werden. https://github.com/facebook/react/issues/2928

Dies sollte auch Fehler beim Auslösen von Methoden behandeln und ordnungsgemäß wiederherstellen. Wie wenn die Methode render() auslöst. Dies ist erheblich mehr Arbeit und wird einige Zeit in Anspruch nehmen, um es richtig zu implementieren, aber die Idee war, die Fehlerbehandlung auf diese Weise zu vereinheitlichen.

Ich würde argumentieren, dass dies außerhalb von React Proper bleiben sollte, und 1 oder 2 wichtige Integrationspunkte sollten mit Projekten wie React-Async und React-Nexus koordiniert werden, damit dies sauber über React Proper erfolgen kann ....

Ich stimme zu, es scheint, als wäre es besser, einen Vorschlag zu haben, um dies zu tun
Backen Sie dies in den Rahmen selbst

Am Di, 21. April 2015, 23:38 Uhr Rodolfo Hansen [email protected]
schrieb:

Ich würde argumentieren, dass dies außerhalb der eigentlichen Reaktion und 1 oder 2 Taste bleiben sollte
Integrationspunkte sollten mit Projekten wie React-Async und koordiniert werden
React-Nexus, damit dies sauber auf React selbst ausgeführt werden kann....


Antworten Sie direkt auf diese E-Mail oder zeigen Sie sie auf GitHub an
https://github.com/facebook/react/issues/3398#issuecomment -95048028.

Am Wochenende habe ich eine weitere Flux-Implementierung namens Flexy erstellt . Überprüfen Sie in diesem den Code für den Laden. Es legt eine .getObservable Methode offen, die der beobachtbaren API entspricht, obwohl sie wirklich keine beobachtbaren oder anderen reaktiven Frameworks verwendet.

Ich würde also sagen, dass die API mit tatsächlichen Observables einfach genug zu erstellen ist.

Beurteilen Sie den Code jedoch nicht zu hart, er wurde an einem Wochenende erstellt für:

  • Spaß
  • Verstehen
  • mit js-csp
  • mit der Observe-API

Nebenbei bemerkt, ein System wie dieses und Flux machen das serverseitige Rendern tatsächlich weniger schmerzhaft. Mit einem ähnlichen System wie React-Nexus können wir Stores initialisieren und an eine React-App übergeben. Wir können dann die Stores und den Dispatcher überwachen und so lange neu rendern, bis keine Aktionen mehr ausgelöst werden (alle erforderlichen Daten befinden sich bereits in den Stores).

Ich würde argumentieren, dass dies der kleinste Integrationspunkt ist, um die neue Semantik zustandsloser Datenabonnements zu erhalten. Welche anderen Integrationspunkte würden Sie vorschlagen? Ohne asynchrones Rendering, das ein viel komplexeres Problem ist und einen eigenen Thread verdient, und dieser Hook könnte unabhängig davon mit asynchronem Rendering verwendet werden.

Alles andere lässt sich bereits komponentenweise auf Basis von React implementieren. Beachten Sie, dass wir keine globalen Injektionen von Plugins durchführen werden, da dies die Wiederverwendung von Komponenten in der Umgebung unterbricht, sodass Frameworks, die auf React aufbauen, kontextabhängig zu einzelnen Komponenten sein sollten.

Welche Hooks vermissen wir?

Hallo,

Um ehrlich zu sein, obwohl neue Hooks die Implementierung einfacher gemacht hätten, können wir sicherlich ohne sie ein seitliches Laden von Daten erreichen, wie wir mit react-async und react-nexus demonstriert haben.

Wenn überhaupt, wäre es hilfreich, den Lebenszyklus von React-Komponenteninstanzen außerhalb einer gemounteten React-Hierarchie verfügbar zu machen und zu unterstützen. In react-nexus verwende ich das interne instanciateReactComponent und rufe selbst componentWillMount , componentWillUnmount usw. auf, und ich halte diesen Ansatz für spröde (was wäre, wenn instanciateReactComponents stützt sich auf interne Invarianten, die sich in der nächsten Version von React ändern?).

Was den zustandslosen Ansatz betrifft, so scheint es mir, dass das Abrufen von asynchronen Daten _is_ zustandsbehaftet ist und somit das Speichern des Status "Ausstehend / Abgeschlossen / Fehlgeschlagen" im Zustand einiger Komponenten relevant ist. Wenn wir react-nexus in realen Apps verwenden, haben wir Komponenten höherer Ordnung, die Daten abrufen und ihren Abrufstatus als Requisiten in ihre untergeordneten Komponenten einfügen. Die innere Komponente ist also „zustandslos“ (was wünschenswert ist), und die äußere Komponente ist „zustandsbehaftet“ (was auch wünschenswert ist, um zB einen Lade-Spinner oder einen Platzhalter anzuzeigen).

Welche anderen Integrationspunkte würden Sie vorschlagen?

Mir scheint, dass @ANDREYPOPP die richtige Frage gestellt hat. Ist nicht das einzige, was wir brauchen, um dies im Userland zu implementieren, der Lifecycle-Hook vor dem Rendern? das scheint die kleinste erforderliche API-Änderung zu sein, der Rest besteht darin, forceUpdate entsprechend einzustellen und auszulösen, wenn Sie data basierend auf dem Eingangsstrom/Emitter/Observable ändern. Es sei denn, ich vermisse etwas anderes Besonderes daran (durchaus möglich)?

Ich lasse mich hier nicht auf die größere Diskussion ein.
Aber um @sebmarkbage zu beantworten, Hooks , die ich möchte, etwas ist, das nur benötigt wird, wenn keine echten Observables verwendet werden.

  • Ein Hook, der Funktionen bereitstellen kann, die mit den Werten umgehen können, die von den Observablen geschoben werden, _bevor_ sie auf Daten gesetzt werden. Mit echten Observablen wäre dies nur ein .map

Wenn die Situation etwas offener wäre, denke ich, dass es Hooks geben sollte, um das Observable-spezifische Verhalten durch benutzerdefinierte Hooks zu ersetzen. Auf diese Weise könnten wir stattdessen Ereignis-Emitter oder CSP-Kanäle verwenden.

Es scheint mir, als ob die letzten paar Kommentare sagen, dass der kleinste Erweiterungspunkt tatsächlich "Verbinden" und "Trennen" von Lebenszyklus-Hooks sind - die es einfach machen, asynchrone Daten an eine Komponente anzuhängen und diese Abonnements zu verwalten?

Observables könnten dann ziemlich trivial darauf aufgebaut werden (innerhalb oder außerhalb des Kerns) – aber die Offenlegung dieser Lebenszykluspunkte hat eine breitere Anziehungskraft?

Ist das eine vernünftige Zusammenfassung?

Ich bin auch noch nicht davon überzeugt, dass dies in React selbst gelöst werden muss. Ich bin eher der Meinung, dass React alle notwendigen Hooks bereitstellen sollte, um diese Funktionalität auf React aufzubauen.

Aber nehmen wir für einen Moment an, wir gehen von observe() . Ein paar Gedanken:

Wir haben this.props , this.state , this.context und jetzt this.data , alle als potenzielle Quellen für neue Daten in render() . Das erscheint mir übertrieben. Ist die Idee, den Anwendungsstatus vom Komponentenstatus zu trennen? Dies könnte einige Probleme im Zusammenhang mit dem Staat klären und trennen, aber ich habe das Gefühl, dass die Kosten für die Einführung eines neuen Inputs die Gewinne möglicherweise nicht überwiegen. Wenn wir möchten, dass sich this.state ausschließlich auf den Komponentenstatus konzentriert, warum lassen wir die Felder in this.data this.context stattdessen zu this.props oder this.context hinzufügen?

Der Name this.data ist zu allgemein. Requisiten sind Daten, Status sind Daten und alle lokalen Variablen sind Daten. Der Name fügt keine Bedeutung hinzu und verwirrt bestehende Bedeutungen. Ich würde this.observed oder jeden anderen Namen, der tatsächlich etwas bedeutet, viel bevorzugen. Also +1 für den Kommentar von @matthewwithanm :

könnte es einen besseren Namen als data ? Beispielsweise würde observed es eindeutiger mit der Methode in Verbindung bringen.

Wenn wir observe() auf dem Server ausführen lassen, brauchen wir eine Art Hook, der alle Speicherlecks beseitigt, die dies verursachen könnte, da ein Unmounten niemals stattfinden wird.

Wenn wir observe() jedes Mal neu aufrufen, wenn sich props oder state ändern (und context ?), dann muss observe performant und idealerweise optimiert werden kann nicht versehentlich teuer werden. Es wird ein Teil des heißen Pfades. Ich mag das von React Nexus:

die nächsten Bindungen werden mit den vorherigen Bindungen unterschieden; Entfernte Bindungen werden abgemeldet, hinzugefügte Bindungen werden abonniert.

Ich bin zu der Überzeugung gelangt, dass der Zustand Komponenten komplizierter macht, und ich habe versucht, ihn weniger in meinen eigenen Komponenten zu verwenden und ihn stattdessen anzuheben . Aus diesem Grund bin ich, zusätzlich zu den von @fisherwebdev geäußerten Bedenken (denen ich jetzt zustimme), nicht davon überzeugt, dass es eine gute Idee ist, observe von state #$ abhängig zu machen. Zustand hängt von Requisiten ab, beobachtet hängt von Zustand _und_ Requisiten ab. Ist das nicht zu kompliziert? Ich hätte lieber observe(props) .

Ich komme auch zu der Erkenntnis, dass Observe nicht von state abhängig sein sollte, vor allem, weil es das nicht muss. Wie @gaearon betont , ist es einfach genug, den Staat auf eine Ebene darüber zu heben, was sich nach dem Trennen von Bedenken einfach sauberer anfühlt. Wenn observe potenziell von state abhängen kann, wird die Logik zur Handhabung von Aktualisierungen innerhalb der Komponente deutlich komplexer; Wenn es nur von props abhängt, können Sie Fork-less Handler in componentDidMount / componentWillReceiveProps haben. Weniger Code im kritischen Pfad führt zu einem einfacheren Renderzyklus und verringert auch die Anzahl unbeabsichtigter Aktualisierungen, die Abonnements erneut auslösen.

+1 für Beobachtung (Requisiten)

Je weniger wir uns mit dem Staat befassen, desto besser meiner Meinung nach.

Ich bin der Meinung, dass React als Bibliothek versuchen sollte, so flexibel wie möglich zu sein. Ich stimme zu, dass es keine gute Idee ist, die Beobachtung vom Zustand abhängig zu machen. Ja, es kann kompliziert sein. Ja, die beste Vorgehensweise sollte sein, nicht vom Staat abhängig zu sein.
Aber das ist eine Wahl, die Benutzer von React treffen dürfen sollten.
Ich würde empfehlen, die aktuelle API unverändert zu lassen (abgesehen von möglichen Hooks, um mehr Flexibilität hinzuzufügen, damit sie beispielsweise mit mehr als nur Observables funktioniert) und eine Dokumentation zu beweisen, die erklärt, dass die Verwendung von state in der Methode Observe nicht empfohlen wird.

Ich glaube, dass jeder das Richtige tun will und dass wir in die richtige Richtung gelenkt werden wollen. Wenn Sie beobachten, dass der Zustand nicht akzeptiert wird, ist dies für den Benutzer einfacher, als versehentlich etwas wie "Dies ist ein Antimuster" in der Dokumentation zu finden.

BEARBEITEN: Entschuldigung, ich habe gerade festgestellt, dass ich nach flatMap gesucht habe. Ich habe mich selbst verwirrt, weil ich dachte, es würde die Reihe von Nachrichten abflachen, aber es arbeitet auf einer höheren Ebene (die Nachrichten sind beobachtbar).

Würde die vorgeschlagene API den Fall handhaben, in dem das Ergebnis eines Datenfelds vom Ergebnis eines anderen abhängt? Dh man bildet das erste Ergebnis ab und gibt ein Observable zurück:

observe(props, context) {
  if (!props.params.threadID) {
    return {};
  }

  const observeThread = ThreadStore.observeGetByID(
    {id: props.params.threadID}
  );
  return {
    thread: observeThread,
    messages: observeThread.map(thread => {
      return MessageStore.observeGetByIDs({ids: thread.messageIDs});
    })
  };
}

Ich bin ziemlich neu in Sachen Observables im Allgemeinen, also gehe ich vielleicht völlig falsch vor. In Promise-Land ist dies extrem einfach, da die Rückgabe eines Promise von then führt, dass nachfolgende then s auf diesem Promise basieren.

Ich verstehe die Kommentare nicht, dass es nicht von this.state abhängen sollte. Der gekapselte Zustand macht React sicherlich viel komplizierter, aber das ist auch alles, was es ist. Wenn es keinen gekapselten Zustand gäbe, bräuchten wir nur eine gespeicherte Bibliothek für den unmittelbaren Modus. Wenn Sie auf "Stores" setzen, dann ja, Sie brauchen keinen Status, aber das ist nicht das, was React vorschreibt.

Wir haben mehrere Muster, für die Sie zusätzliche Wrapper erstellen müssen, aber für den normalen Gebrauch sollten Sie dies nicht brauchen. Abgesehen von Speichern hängen Ihre beobachteten Daten immer vom Zustand ab, auch wenn indirekt. Ich glaube nicht, dass es schlecht ist, sich beim Beobachten darauf zu verlassen. Z.B

observe() {
  return { items: Items.getPagedItems({ pageIndex: this.state.currentPage }) };
}

Selbst wenn observe nicht von state abhängen würde, würde es immer noch von props und context abhängen. Selbst wenn es nicht von context abhängen würde, hätten Sie immer noch eine Indirektion, die context verwendet, um die Props einer Komponente zu rendern, die es in observe verwendet.

observe müsste jedes Mal neu bewertet werden, wenn ein Renderdurchgang herunterkommt, weil sich die Requisiten geändert haben könnten. Wir würden jedoch die resultierenden Observablen definitiv unterscheiden und nicht abbestellen/erneut abonnieren, wenn das gleiche Observable zurückgegeben wird. Wir können jedoch keine Unterschiede bei einzelnen Eigenschaften machen (außer mit shouldComponentUpdate), daher sollten Sie idealerweise Ihren eigenen Cache mit Map als Power-Feature implementieren. Auf diese Weise können Sie dasselbe Observable an mehrere Komponenten im Baum zurückgeben. B. mehrere Komponenten, die denselben Benutzer laden. Selbst wenn Sie dies nicht tun, erstellen Sie einfach das Observable neu und treffen schließlich auf den unteren Cache. So funktioniert React Reconciliation sowieso. Es ist nicht so langsam.

Dieser observe -Hook ist nicht darauf ausgelegt, React in dem Sinne vollständig reaktiv zu machen, dass der Zustand in Observables erfasst wird. Derzeit ist es ein wichtiges Designziel, das Einfangen von Zuständen in Closures und Kombinatoren zu vermeiden und stattdessen einen schönen, sauberen, separaten Zustandsbaum zu haben, der eingefroren und wiederbelebt und möglicherweise von allen Workern gemeinsam genutzt werden kann.

Was mich zu meinem letzten Punkt bringt...

Wir _brauchen_ dies sicherlich nicht zur Kernbibliothek hinzuzufügen. Die ursprüngliche "öffentliche" Schnittstelle war mountComponent/receiveComponent und Sie konnten Ihr gesamtes zusammengesetztes Komponentensystem darauf aufbauen. Allerdings haben nicht viele Leute verwendet, dass es viel mächtiger ist, die Abstraktionsleiste zu erhöhen, da wir jetzt andere Dinge bauen können, die durch eine höhere Abstraktionsleiste ermöglicht werden. Wie zum Beispiel komponentenweite Optimierungen.

Der Hauptzweck von React besteht darin, einen Vertrag zwischen Komponenten zu erstellen, damit verschiedene Abstraktionen im Ökosystem nebeneinander existieren können. Ein wichtiger Teil dieser Rolle besteht darin, die Abstraktionsebene für gemeinsame Konzepte zu erhöhen, damit wir neue komponentenübergreifende Funktionen ermöglichen können. ZB den gesamten Zustand eines Teilbaums speichern, dann den Teilbaum wiederbeleben. Oder möglicherweise das automatische Unmounten auf dem Server oder das Ändern der Timing-Aspekte der Abstimmung auf dem Server.

Es ist auch wichtig, einige Batterien im Lieferumfang enthalten, um all dies schmackhaft und einheitlich zu machen.

Es ist wichtig zu erkennen, dass die Mikromodularisierung (wie das Hinzufügen eines neuen Lebenszyklus-Hooks) nicht unbedingt ein reiner Gewinn gegenüber dem Einbau in das Framework ist. Es bedeutet auch, dass es nicht länger möglich ist, über systemweite Abstraktionen nachzudenken.

Ich wünschte, so etwas stünde in den Dokumenten als „Philosophie/Designziele/Nichtziele“.

Der Hauptzweck von React besteht darin, einen Vertrag zwischen Komponenten zu erstellen, damit verschiedene Abstraktionen im Ökosystem nebeneinander existieren können. Ein wichtiger Teil dieser Rolle besteht darin, die Abstraktionsebene für gemeinsame Konzepte zu erhöhen, damit wir neue komponentenübergreifende Funktionen ermöglichen können.

Ich liebe das. Ich stimme @gaearon zu, dass es schön wäre, wenn dies in einer Art Dokument enthalten wäre.

Wir müssen dies sicherlich nicht zur Kernbibliothek hinzufügen ... Allerdings haben nicht viele Leute verwendet, dass es viel mächtiger ist, die Abstraktionsleiste zu erhöhen, da wir jetzt andere Dinge bauen können, die durch eine höhere Abstraktionsleiste ermöglicht werden. Wie zum Beispiel komponentenweite Optimierungen.

Ich habe das Gefühl, dass Zurückhaltung (zumindest für mich) nicht darin besteht, eine weitere API hinzuzufügen, sondern eine hinzuzufügen, die von einem nicht sprachlichen (noch in Definition befindlichen) Konstrukt abhängt, um zu funktionieren. Das könnte völlig gut funktionieren, aber ich mache mir Sorgen über die Probleme, mit denen Promise-Bibliotheken konfrontiert sind, bei denen keine Promise-Bibliothek (selbst eine mit Spezifikationsbeschwerden) einander vertrauen kann, was zu unnötigem Wrapping und Abwehrarbeit führt, um sicherzustellen, dass sie korrekt gelöst werden, was die Optimierungsmöglichkeiten einschränkt . Oder schlimmer noch, Sie bleiben wie jQuery mit einer kaputten Implementierung stecken, die sich niemals ändern kann.

@jquense Ich stimme

Das Zögern vor zwei Jahren war, dass man noch zu weit von einer Standardisierung entfernt sei. Ich wollte ein Standardprotokoll, bevor wir es zum Kern hinzufügten.

Ich denke, wir kommen an einen Punkt, an dem sich viele Frameworks über die Notwendigkeit von etwas wie Observables einig sind, und die Standardisierung erreicht einen Punkt, an dem eine schmackhafte API vorgeschlagen wurde. Ich bin mir sicher, dass wir es am Ende ein wenig optimieren müssen, aber solange die High-Level-Architektur funktioniert, sollte sie austauschbar sein und schließlich konvergieren.

Ich denke, was mit Promises passiert ist, ist, dass die API- und Debugging-Story in bestimmten Bereichen, unter denen Observables nicht leidet, stark gefehlt hat. Es ist eine vollständigere Geschichte, bei der Promises eine minimale unvollständige Lösung standardisieren musste.

Die einzige Meinungsverschiedenheit bezüglich: Observables, die ich beobachtet habe (konnte nicht widerstehen, tut mir leid), ist das Zalgo-Potenzial. Ob Observables den Wert als Reaktion auf ein Abonnement synchron pushen können oder nicht. Einige Leute scheinen dagegen zu sein, aber die Verwendung von Observables durch React wird davon abhängen, soweit ich verstehe. Können Sie dazu etwas sagen?

Im Allgemeinen sehe ich Zalgo nicht als Problem mit Observables an, da der Verbraucher immer die Kontrolle hat und sich für Always-Async mit etwas wie observeOn .

Schön, dass es hier endlich einen Konsens gibt. Ich persönlich bevorzuge Kanäle gegenüber Observables, aber wenn Observables zur Sprache hinzugefügt werden, muss ich nicht länger warten.

Stellen wir jedoch sicher, dass wir die API offen genug halten, um mit Nicht-Observables zu arbeiten, die der grundlegenden API entsprechen.

Zalgo finde ich auch kein Problem. Mit Observables können Sie jedoch bei Bedarf einen Planer verwenden, um eine Asynchronität sicherzustellen. Der Standardplaner ist jetzt asynchron, sodass Sie ihn nach Bedarf verwenden können.

@sebmarkbage Ich denke, Sie haben die meisten meiner Bedenken angesprochen, und ich sehe jetzt den Vorteil, dies dem Framework hinzuzufügen. Können Sie jedoch this.data kommentieren -- (1) können/sollten wir diese Felder in props/context/state falten oder (2) können wir sie umbenennen?

Etwas Bikeshed-y, aber trotzdem los ... bei Zalgo geht es nicht wirklich darum, ob es in Bezug auf die API-Erwartung von Bedeutung ist, sondern um beobachtbare Interop und einfache Implementierung. Keine frühzeitige Einigung über Zalgo zu haben, was die Welt der Promise-Bibliotheken in die ärgerliche Lage gebracht hat, im Umgang mit Promises von anderen Bibliotheken super defensiv sein zu müssen. (mein obiger Punkt wird unten wiederholt)

...wo keine Promise-Bibliothek (auch keine Spezifikationsbeschwerden) einander vertrauen kann, was zu unnötigem Wrapping und Verteidigungsarbeit führt, um sicherzustellen, dass sie korrekt aufgelöst werden

Da frühe Versprechungen nicht alle der asynchronen Auflösung entsprachen, befinden wir uns jetzt in einer Position, in der selbst spezifizierte Bibliotheken nicht davon ausgehen können, dass thenables vertrauenswürdig sind, was potenzielle Optimierungen zunichte macht. Dies scheint mir hier besonders relevant zu sein, wo React keine zu verwendende Observable-Implementierung bereitstellen wird (wer würde das schon wollen?), und wir sind höchstwahrscheinlich Jahre davon entfernt, uns ausschließlich auf eine von einem Browser bereitgestellte Observable-, also einfache Bibliothek verlassen zu können Interoperabilität ist wichtig. Plus zu @gaearon 's Punkt, wenn React von Sync-Aufrufen abhängt und es so spezifiziert ist, dass es immer asynchron ist, bringt uns das in eine jquery-ähnliche Position, in der wir mit einer Rogue-Implementierung feststecken.

Ich stimme vollkommen zu. Ich wollte diesen Haken schon vor langer Zeit hinzufügen. Das Zögern vor zwei Jahren war, dass man noch zu weit von einer Standardisierung entfernt sei. Ich wollte ein Standardprotokoll, bevor wir es zum Kern hinzufügten.

Ich bin froh, dass auch daran gedacht und gedacht wird, das ist sicherlich beruhigend. :) und im Allgemeinen denke ich, dass die frühe Annahme von Versprechungen die Nachteile wert war, die ich hier bespreche, also verstehen Sie meine Bedenken nicht als Abneigung oder Missbilligung, ich bin ziemlich aufgeregt über die Aussicht auf eine erstklassige API dafür und ich sehe auch, dass Observable hier wirklich eine gute/vernünftigste Wahl dafür ist.

„Wir sollten den RxJS-Vertrag von Observable verwenden, da dieser häufiger verwendet wird und eine synchrone Ausführung ermöglicht, aber sobald der Vorschlag von @jhusain häufiger verwendet wird, werden wir stattdessen zu diesem Vertrag wechseln.“

Nur um ein bisschen mehr Kontext hinzuzufügen. Es gibt eine Reactive Streams-Initiative (http://www.reactive-streams.org/), um einen Standard für die asynchrone Stream-Verarbeitung mit nicht blockierendem Gegendruck bereitzustellen. Dies umfasst Bemühungen, die auf Laufzeitumgebungen (JVM und JavaScript) sowie Netzwerkprotokolle abzielen.

Die aktuell führenden Implementierungen sind zB Akka Streams oder RxJava. Ich weiß nicht, ob RxJs jetzt bereits der gleichen Schnittstelle entsprechen, der aktuellen Schnittstelle für Abonnentenist onSubscribe(Subscription s), onNext(T t), onCompleted(), onError(Throwable t).

Kannst du mehr Licht in den Vorschlag von @jhusain bringen ?

Ich weiß nicht, ob React sich strikt an diese Initiative halten sollte, denn wenn ich es brauche, kann ich wahrscheinlich RxJs dazwischenlegen (vorausgesetzt, es wird sich daran halten) und mich an die React-Schnittstelle anpassen und fortgeschrittenere Konzepte wie Gegendruck auf RxJs lassen (obwohl ich würde lieber nicht viel anpassen müssen).

Gibt es eine Position oder ein Ziel bezüglich dieser Initiative?

@vladap Ich glaube, das ist der erwähnte Vorschlag von @jhusain

Ich habe @jhusain durchgelesen und bin mir nicht sicher, warum ich in Zukunft möglicherweise zu dieser Spezifikation wechseln sollte. Gibt es einen konkreten Vorteil?

Die Reactive-Streams-Spezifikation hat eine größere Unterstützung und ist bereits in Version 1.0 . Da RxJava diese Spezifikation bereits implementiert, gehe ich davon aus, dass RxJs folgen werden (habe es aber nicht überprüft).

Dieser Blog fasst die Schnittstellen mit einigen Beispielen unter Verwendung von Akka-Streams zusammen.

Ich sehe einen Vorteil darin, die gleichen Schnittstellen im Backend und im Frontend zu haben, hauptsächlich weil ich an beiden arbeite. Möglicherweise hilft es, zwischen Backend- und Frontend-Gruppen zu kooperieren, aber andererseits gehe ich davon aus, dass Websocket oder sse die eigentlichen Integrationspunkte für das Streaming sind.

Ich kann die Liste der Implementierer im Moment nicht auf www.reactive-streams.org finden, aber das letzte Mal, als ich sie überprüft habe, war:

Björn Antonsson – Typesafe Inc.
Gavin Bierman – Oracle Inc.
Jon Brisbin – Pivotal Software Inc.
George Campbell – Netflix, Inc
Ben Christensen – Netflix, Inc
Mathias Dönitz – spray.io
Marius Eriksen – Twitter Inc.
Tim Fox – Red Hat Inc.
Viktor Klang – Typesafe Inc.
Dr. Roland Kuhn – Typesafe Inc.
Doug Lea – SUNY Oswego
Stéphane Maldini – Pivotal Software Inc.
Norman Maurer – Red Hat Inc.
Erik Meijer – Applied Duality Inc.
Todd Montgomery – Kaazing Corp.
Patrik Nordwall – Typesafe Inc.
Johannes Rudolph – spray.io
Endre Varga – Typesafe Inc.

Vielleicht gehe ich hier zu weit, aber ich glaube, dass der größere Kontext bei zukünftigen Entscheidungen helfen kann.

@vladap Nach dem, was ich verstehe und was ich in den Github-Problemen sehe, arbeitet @jhusain bereits mit ihnen, also denke ich, dass wir nicht so viele Probleme haben werden.
Aus der Sicht der Benutzeroberfläche, was ich auch in verschiedenen Github-Problemen und anderen Spezifikationsdokumenten erfassen konnte, wird der Beobachter die Generatorschnittstelle sicherlich respektieren, also so etwas wie:

{
  next(value),
  throw(e),
  return(v)
}

Die einfache Implementierung eines sehr einfachen Observable mit einer einzigen "Subscribe"-Methode, die diese Schnittstelle respektiert, sollte für die Reaktion sicher sein.

Es sieht aus wie nur ein anderer Name mit der gleichen Funktionalität, in diesem Sinne ist es in Ordnung. Ich würde wahrscheinlich die gleiche Benennung wie in der Spezifikation bevorzugen, aber letztendlich ist es mir egal, soweit diese Methoden dasselbe tun.

Ich kann das fehlende Äquivalent zu onSubscribe() nicht auswerten. In dem von mir erwähnten Blog sagt der Autor, dass dies ein Schlüssel zur Kontrolle des Gegendrucks ist. Ich weiß nicht, dass es andere Anwendungsfälle hat. Daraus gehe ich davon aus, dass React sich nicht um die Kontrolle des Gegendrucks kümmert oder es eine andere Strategie dafür gibt. Es ist eine komplexe Sache, daher verstehe ich, dass es sich nicht um React handelt.

Verstehe ich richtig, dass die Strategie etwas in der Art ist - entweder ist die App komplex und es können Gegendruckprobleme auftreten, dann verwenden Sie etwas dazwischen, um sie zu lösen, wie RxJS, oder Sie verbinden die React-Komponente direkt mit z. B. Websocket und ziehen Sie sie an Ich habe keine Gegendruckprobleme, weil die App einfach ist und langsame Updates hat.

Wo finde ich vorgeschlagene beobachtbare Schnittstellen für das zukünftige ECMAScript? Falls schon vorhanden.

Den aktuellen Vorschlag finden Sie hier:

https://github.com/jhusain/asyncgenerator

JH

Am 7. Mai 2015 um 2:32 Uhr schrieb vladap [email protected] :

Wo finde ich vorgeschlagene beobachtbare Schnittstellen für das zukünftige ECMAScript? Falls schon vorhanden.


Antworten Sie direkt auf diese E-Mail oder zeigen Sie sie auf GitHub an.

Der Reactive Streams Proposal (RSP) geht weiter als der TC-39-Vorschlag, da er ein Observable einführt, das den Gegendruck handhabt. Das RSP Observable ist für das effiziente Senden von Streams über das Netzwerk optimiert und berücksichtigt dabei den Gegendruck. Es basiert teilweise auf der Arbeit, die in RxJava geleistet wurde, was ein sehr beeindruckendes Stück Technik ist (vollständige Offenlegung: es wurde von dem Kollegen bei Netflix, Ben Christensen, entworfen).

Der Hauptgrund für die Entscheidung, den primitiveren Observable-Typ zu standardisieren, ist Vorsicht. Das primitivere Observable ist das Dual des ES2015 Iterable-Vertrags, der uns wertvolle Garantien dafür gibt, dass der Typ mindestens so flexibel ist wie ein bereits in ES2015 standardisierter Typ. Darüber hinaus gibt es in JS eine Vielzahl von Anwendungsfällen für Observables, die keinen Gegendruck erfordern. Im Browser ist das DOM die häufigste Senke für Push-Streams und fungiert effektiv wie ein Puffer. Da der RSP-Typ komplexer ist, besteht unser Ansatz darin, zuerst den primitiveren Typ zu standardisieren und dann Raum zu lassen, um den fortgeschritteneren Typ später zu implementieren. Idealerweise würden wir warten, bis es im Benutzerland validiert wurde.

FYI RxJS hat derzeit keine Pläne, RSP Observable zu implementieren.

JH

Am 7. Mai 2015 um 2:30 Uhr schrieb vladap [email protected] :

Es sieht aus wie nur ein anderer Name mit der gleichen Funktionalität, in diesem Sinne ist es in Ordnung. Ich würde wahrscheinlich die gleiche Benennung wie in der Spezifikation bevorzugen, aber letztendlich ist es mir egal, soweit diese Methoden dasselbe tun.

Ich kann das fehlende Äquivalent zu onSubscribe() nicht auswerten. In dem von mir erwähnten Blog sagt der Autor, dass dies ein Schlüssel zur Kontrolle des Gegendrucks ist. Ich weiß nicht, dass es andere Anwendungsfälle hat. Daraus gehe ich davon aus, dass React sich nicht um die Kontrolle des Gegendrucks kümmert oder es eine andere Strategie dafür gibt. Es ist eine komplexe Sache, daher verstehe ich, dass es sich nicht um React handelt.

Verstehe ich richtig, dass die Strategie etwas in der Art ist - entweder ist die App komplex und es können Gegendruckprobleme auftreten, dann verwenden Sie etwas dazwischen, um sie zu lösen, wie RxJS, oder Sie verbinden die React-Komponente direkt mit z. B. Websocket und ziehen Sie sie an Ich habe keine Gegendruckprobleme, weil die App einfach ist und langsame Updates hat.


Antworten Sie direkt auf diese E-Mail oder zeigen Sie sie auf GitHub an.

Vielen Dank für die wertvollen Details. Es macht sehr viel Sinn.

@gaearon Ich habe dich irgendwie kopiert. Ich wollte ParseReact mit ES6-Klassen verwenden, also musste ich die Observe-API des Mixins als Komponente höherer Ordnung neu implementieren.

https://gist.github.com/amccloud/d60aa92797b932f72649 (Verwendung unten)

  • Ich erlaube, dass Observe auf der Komponente definiert oder an den Decorator übergeben wird. (Ich könnte die beiden zusammenführen)
  • Ich füge Observables als Requisiten ein (keine this.data oder this.props.data)

@aaronshaf @gaearon Der Vorteil, es erstklassig zu machen, ist:

1) Es nagt nicht am Requisiten-Namensraum. ZB muss die Komponente höherer Ordnung keinen Namen beanspruchen wie Daten aus Ihrem Props-Objekt, die für nichts anderes verwendet werden können. Das Verketten mehrerer Komponenten höherer Ordnung verschlingt immer mehr Namen, und jetzt müssen Sie einen Weg finden, diese Namen eindeutig zu halten. Was ist, wenn Sie etwas komponieren, das möglicherweise bereits komponiert wurde, und jetzt einen Namenskonflikt haben?

Außerdem denke ich, dass die beste Vorgehensweise für Komponenten höherer Ordnung darin bestehen sollte, den Vertrag der umhüllten Komponente nicht zu ändern. Dh konzeptionell sollten die gleichen Requisiten rein wie raus sein. Andernfalls ist es verwirrend, zu verwenden und zu debuggen, wenn der Verbraucher einen völlig anderen Satz von Requisiten liefert, als er erhält.

Anstelle einer HOC kann es eine reguläre Komponente sein:

import Observe from 'react/addons/Observe';

class Foo {
  render() {
    return (
      <Observe
        render={this.renderData}
        resources={{
          myContent: xhr(this.props.url)
        }} />
    );
  }

  renderData({ myContent }) {
    if (myContent === null) return <div>Loading...</div>;
    return <div>{myContent}</div>;
  }
}

Da Sie die als render Prop übergebene Funktion steuern, können die Namen nicht kollidieren. Andererseits verschmutzt es nicht den Zustand des Eigentümers.

Was fehlt mir hier?

Dies könnte sogar weniger ausführlich sein, wenn die Observe -Komponente nur Observables von ihren Requisiten geholt hätte:

render() {
  return (
    <Observe myContent={Observable.fetch(this.props.url)}
             render={this.renderData} />
  );
}

renderData({ myContent }) {
  if (myContent === null) return <div>Loading...</div>;
  return <div>{myContent}</div>;
}

Das Schöne daran ist auch, dass Neuanmeldungen vermieden werden, wenn shouldComponentUpdate falsch zurückgegeben wird, da wir überhaupt nicht in render hineinkommen.

Schließlich könnte man einen Decorator für render schreiben, der es in eine Observe Komponente verpackt:

@observe(function (props, state, context) {
  myContent: Observable.fetch(props.url)
})
render({ myContent }) {
  if (myContent === null) return <div>Loading...</div>;
  return <div>{myContent}</div>;
}

Ich würde es vorziehen, render als reine Rendering-Funktion zu behalten, anstatt Datenabruflogik darin einzufügen.
Der ursprüngliche Vorschlag scheint meiner Meinung nach gut zu sein. Es ist sehr ähnlich, wie der Zustand mit rx-react funktioniert, und es ermöglicht, die Zustandsverwaltung von der Logik zum Abrufen von Daten zu trennen, was sehr kohärent erscheint.

Das einzige, was mich stört, ist die Verwendung einer Observable-Karte anstelle einer Observable, da der Benutzer nicht die Möglichkeit hat, zu wählen, wie Observable zusammengesetzt wird, aber das ist ein kleines Problem.

Es fügt nicht wirklich die Datenabruflogik ein, sondern spart nur etwas Tipparbeit. Es wird in die obige Version entzuckert, die nur die Komponente <Observe /> rendert. Es ist nicht ungewöhnlich, zustandsbehaftete Komponenten zu rendern, daher glaube ich nicht, dass render weniger rein ist als jetzt.

Ich habe versucht, das Beste aus #3858 und diesem Vorschlag zu kombinieren.

Bei allen HOC-Ansätzen ist der Vorteil die Explizitheit, aber die Nachteile werden von @sebmarkbage beschrieben : Die Requisitennamen können irgendwann in Konflikt geraten.

Im aktuellen Vorschlag liegt der Vorteil in der Explizitheit, aber die negative Seite ist der kompliziertere Lebenszyklus und die größere API-Oberfläche der Kernkomponenten.

In #3858 besteht der Vorteil darin, "memoized render"-Abhängigkeiten mit dem Rendering selbst zu verbinden (sie werden nirgendwo anders verwendet, also macht es Sinn), aber ich mache mir Sorgen um das "sieht synchron aus, ist aber asynchron" und verstehe nicht, wie es funktioniert kann mit unveränderlichen Modellen arbeiten, wenn es so sehr auf this angewiesen ist . Es reibt mich auch falsch auf die Art und Weise, wie React einfach zu begründen war, weil es nicht einfach ist, über das manuelle Nachverfolgen von Änderungen und das Koppeln der Datenquellen mit React (oder das Umschließen mit React) nachzudenken. Ich verstehe, dass es einen Druck gibt, etwas zu implementieren, das sowohl leistungsfähig ist als auch die Boilerplate reduziert.

In meinem Vorschlag behalte ich die Kollokation und Explizitheit bei, aber:

  • <Observe /> (oder der observe() Decorator, der Render mit <Observe /> umschließt) ist nur ein Add-On, ich schlage keine _irgendwelche_ Änderungen am React-Kern vor.
  • Jeder kann seine eigene Beobachtungslogik für seine Anwendungsfälle implementieren. Sie können Ihr eigenes <Observe /> , das nur eine Observable verwendet, wenn Sie das bevorzugen. Sie können den Decorator verwenden oder nicht.
  • Der Lebenszyklus der Komponenten bleibt gleich.
  • Es gibt keine Prop-Konflikte, da die Daten als Parameter übergeben werden.
  • Wir betreiben Dogfooding, indem wir die Probleme mit den Werkzeugen lösen, die wir haben.
  • Zur Reduzierung der Boilerplate verwenden wir Tools zur Boilerplate-Reduktion (Decorators), anstatt neue Kernkonzepte einzuführen.

Tolle Diskussion und Arbeit hier überall, viel Respekt. :)

Ich stimme zu, dass dies es nicht verdient, es in den Kern zu schaffen. Vielleicht gibt ein Add-On diesem Vorschlag genug Zugkraft, damit die Leute versuchen können, ihn zu konvergieren und zu standardisieren, bevor sie sich vollständiger verpflichten. Davon abgesehen finde ich diesen Vorschlag wegen seines Minimalismus besser als https://github.com/facebook/react/issues/3858 und https://github.com/facebook/react/pull/3920 .

Dies ist etwas, das ich für Nebenprojekte verwendet habe (also Salzkörner) - es ähnelt der großartigen Arbeit von @elierotenberg ,

Bereiten Sie sich auf CoffeeScript und Mixins vor oder blinzeln Sie weiter, bis dies wie ES6 aussieht, wenn Sie dies bevorzugen. :)

_ = require 'lodash'

module.exports = DeclareNeedsMixin = 
  componentDidMount: ->
    <strong i="12">@needsConsumerId</strong> = _.uniqueId @constructor.displayName
    <strong i="13">@sinkNeeds</strong> <strong i="14">@props</strong>, <strong i="15">@state</strong>

  componentWillUpdate: (nextProps, nextState) ->
    <strong i="16">@sinkNeeds</strong> nextProps, nextState

  componentWillUnmount: ->
    @props.flux.declareNeeds <strong i="17">@needsConsumerId</strong>, []

  sinkNeeds: (props, state) ->
    if not @declareNeeds?
      return console.warn 'Missing method required for DeclareNeedsMixin: `declareNeeds`', @

    needs = <strong i="18">@declareNeeds</strong> props, state
    props.flux.declareNeeds <strong i="19">@needsConsumerId</strong>, needs

  # Intended to be overridden by the host class.
  # Returns a set of facts, stored as an array.
  # Yes, immutable data is awesome, that's not the point here though. :)
  # Facts are serializable data, just values.
  # declareNeeds: (props, state) ->
  #   []

Und so verwendet:

module.exports = EmailThreads = React.createClass
  displayName: 'EmailThreads'
  mixins: [DeclareNeedsMixin]

  propTypes:
    flux: PropTypes.flux.isRequired

  declareNeeds: (props, state) ->
    [Needs.GmailData.myThreads({ messages: 20 })]

  ...

declareNeeds ist also eine Einwegfunktion von Requisiten und Zustand zu einer Beschreibung dessen, was diese Komponente benötigt. In der tatsächlichen Implementierung hier versenkt das empfangende Ende von @props.flux.declareNeeds , das auf einer Komponente der obersten Ebene eingerichtet ist, diese Anforderungen in ein ProcessSink -Objekt. Es stapelt nach Belieben, dedupliziert needs über Komponenten hinweg, die dasselbe flux teilen, und führt dann Nebeneffekte aus, um diese Anforderungen zu erfüllen (wie das Herstellen einer Verbindung zu einem Socket oder das Senden von HTTP-Anforderungen). Es verwendet die Referenzzählung, um zustandsbehaftete Dinge wie Socket-Verbindungen zu bereinigen, wenn es keine Komponenten mehr gibt, die sie benötigen.

Daten fließen von zustandsbehafteten Bits wie Socket-Ereignissen und Anforderungen in den Dispatcher (und dann zu Stores oder wo auch immer) und zurück zu den Komponenten, um deren Anforderungen zu erfüllen. Dies ist nicht synchron, und daher übernehmen alle Komponenten das Rendern, wenn noch keine Daten verfügbar sind.

Ich teile dies hier nur als weitere Stimme dafür, dass die Erforschung dieser Art von Lösungen im Benutzerbereich möglich ist und dass die aktuelle API diese Art von Experimenten wirklich gut unterstützt. In Bezug auf einen minimalen Schritt, den der Kern unternehmen könnte, um die Interoperabilität zwischen verschiedenen Ansätzen zu unterstützen, denke ich, dass @elierotenberg es auf den

Wenn überhaupt, würde es hilfreich sein, den Lebenszyklus von React-Komponenteninstanzen außerhalb einer gemounteten React-Hierarchie verfügbar zu machen und zu unterstützen.

Was den zustandslosen Ansatz betrifft, so scheint mir, dass das Abrufen von asynchronen Daten zustandsbehaftet ist und daher das Speichern des Status „Ausstehend/abgeschlossen/fehlgeschlagen“ im Zustand einiger Komponenten relevant ist.

@elierotenberg und @andrewimm machen ein hervorragendes @sebmarkbage Ich denke, Ihr Instinkt für den minimalen Interop-Punkt ist richtig, aber ich bin mir nicht sicher, wie das Hinzufügen von Semantik für Fehler, um den Komponentenbaum aufzublähen, dieser Anforderung entspricht. Hier muss es eine ebenso einfache Geschichte geben, wie man auf Werte von onError und onCompleted , selbst wenn es nur so ist, dass Slots in this.observed den letzten Wert von entweder next/error/completed wie { next: "foo" } . Ohne den beobachtbaren Vertrag als erstklassiges Feature zu unterstützen, bin ich etwas skeptisch, was diesen Vorschlag betrifft.

Und da dies das Internet ist und ich mich hier zum ersten Mal einmische: Der React-Issues-Feed ist eine der besten Lektüre und eine umfassende Quelle für großartige Arbeit und Ideen. :+1:

riecht für mich nach Bindungen.

Ich bin mir nicht sicher, wie sich dies auf den aktuellen Vorschlag/die aktuelle Implementierung bezieht, aber ich habe festgestellt, dass das Ermöglichen von Komposition und Manipulation höherer Ordnung tatsächlich ein Schlüsselmerkmal der Datenabhängigkeitsverfolgung ist, insbesondere wenn Sie eine reaktive Datenquelle verwenden (Flux, Flux über das Kabel oder irgendetwas anderes, das Sie nicht mit Updates versorgt).

Nehmen Sie das folgende Beispiel mit react-nexus@^3.4.0 :

// the result from this query...
@component({
  users: ['remote://users', {}]
})
// is injected here...
@component(({ users }) =>
  users.mapEntries(([userId, user]) =>
    [`user:${userId}`, [`remote://users/${userId}/profile`, {}]]
  ).toObject()
))
class Users extends React.Component {
  // ... this component will receive all the users,
  // and their updates.
}

Insgesamt frage ich mich, ob es überhaupt eine Datenbindung in der Komponenten-API geben sollte. Es scheint mir, dass Dekoratoren, die Komponenten höherer Ordnung zurückgeben, eine sehr schöne Möglichkeit bieten, die Datenbindung auszudrücken, ohne den Namespace der Komponentenmethoden zu verschmutzen.

Wie @sebmarkbage feststellte, besteht jedoch die Gefahr, dass stattdessen der Requisiten-Namespace verschmutzt wird. Im Moment verwende ich einen Props Transformer Decorator ( react-transform-props ), um Requisiten zu bereinigen/umzubenennen, bevor ich sie an die innere Komponente übergebe, aber ich erkenne an, dass es in Zukunft problematisch werden könnte, wenn Komponenten höherer Ordnung werden üblicher und das Risiko von Namenskonflikten steigt.
Könnte dies gelöst werden, indem Symbole verwendet werden, die Eigenschaftsschlüssel sind? Unterstützt die propTypes Überprüfung Symbol -verschlüsselte Requisiten? Wird JSX berechnete Inline-Requisitenschlüssel unterstützen (obwohl man immer berechnete Eigenschaften + Objektverteilung verwenden kann)?

Tut mir leid, wenn dies ein wenig vom Thema abweicht, aber es scheint mir, dass wir immer noch die richtige süße Abstraktion/API finden müssen, um Daten auf Komponentenebene auszudrücken.

meine 2 ¢

Ich habe viel Spaß mit dem Muster „Kinder als Funktion“ für Werte, die sich im Laufe der Zeit ändern. Ich glaube, @elierotenberg hat es sich zuerst

<Springs to={{x: 20, y: 30}} tension={30}>
  {val => <div style={{left: val.x, top: val.y}}>moving pictures</div>}
</Springs>

Keine Requisitenkollision und keine Verwendung des Eigentümerstatus. Ich kann auch mehrere Federn verschachteln und die Reaktion verwaltet alle harten Teile. Diese 'Observables' (ha!) könnten auch onError , onComplete und andere Requisiten (Graphql-Abfragen?) akzeptieren.

Ein grober Versuch, dies für "Flux" zu skizzieren

<Store 
  initial={0}
  reduce={(state, action) => action.type === 'click'? state+1 : state} 
  action={{/* assume this comes as a prop from a 'Dispatcher' up somewhere */}}> 
    {state => <div onClick={() => dispatch({type: 'click'})}> clicked {state} times</div>}
</Store>

Wir haben dieses Muster, das wir _render callbacks_ nannten, bei Asana verwendet, entfernen uns aber letztendlich davon.

Vorteile

  • Keine Kollisionsgefahr von Stützen.
  • Sowohl als Autor von StoreComponent als auch als Benutzer von StoreComponent einfach zu implementieren

Nachteile

  • shouldComponentUpdate ist extrem schwierig zu implementieren, da der Render-Callback möglicherweise über dem Status geschlossen wird. Stellen Sie sich vor, wir hätten einen Komponentenbaum von A -> Store -> B . A hat einen Zähler in seinem Zustand, auf den während des Render-Callbacks als Requisiten für B zugegriffen wird. Wenn A aufgrund des Zählers aktualisiert wird und Store nicht aktualisiert wird, hat B eine veraltete Version des Zählers. Infolgedessen waren wir gezwungen, den Store immer zu aktualisieren.
  • Das isolierte Testen von Komponenten wurde sehr schwierig. Unweigerlich fügten Entwickler komplexe Logik in die Render-Callbacks für die zu rendernde Komponente ein und wollten die Logik testen. Der natürliche Weg, dies zu tun, bestand darin, den gesamten Baum zu rendern und _durch_ die Store-Komponente zu testen. Dies machte es unmöglich, den flachen Renderer zu verwenden.

Wir wechseln jetzt zu einem Modell, bei dem StoreComponent ReactElement und wenn der Store neue Daten erhält, klont der Store das ReactElement, das eine bestimmte Requisite überschreibt. Es gibt viele Möglichkeiten, dieses Muster auszuführen, z. B. indem Sie einen Komponentenkonstruktor und Requisiten verwenden. Wir haben uns für diesen Ansatz entschieden, weil er am einfachsten in TypeScript zu modellieren war.

Tolle Punkte, ich kann mir auch keinen Weg vorstellen, ohne die Anforderung "seitwärts" zu brechen.

@threepointone Klingt genau wie einer der Implementierungsvorschläge zu Power https://github.com/reactjs/react-future/pull/28

@pspeter3 Gibt es in Ihrem Beispiel einen ernsthaften Nachteil, shouldComponentUpdate: ()=> true ? Ich denke, seine 'Kinder' wären sowieso ohne Store in der Kette gerendert worden. Vielen Dank für Ihre Zeit!

@threepointone Genau das haben wir für unsere Grenzen gemacht. Es war unklar, was genau die Auswirkungen waren, aber es gab Leistungssorgen von Mitgliedern des Teams. Die Sorgen in Verbindung mit dem Schwierigkeitstest zwangen den Wechsel zu React.cloneElement(this.props.child, {data: this.state.data})

@pspeter3 Der Testwinkel ist definitiv ein Problem. Was ist, wenn shallowRenderer 'Render Callbacks' erkennt? Würde das helfen?

Ps- 'Rückruf rendern' :thumbs_up:

@sebmarkbage Die aktuelle Diskussion über es-observable ist, dass subscribe garantiert asynchron sein würde, mit einer [Symbol.observer] Methode, die bereitgestellt wird, um synchron zu verkürzen und zu abonnieren, obwohl die Gültigkeit sowohl synchroner als auch asynchroner APIs besteht wird derzeit diskutiert.

Ich hatte mich auf dieses Ticket mit dem oben genannten Anwendungsfall zugunsten des synchronen Abonnements eingelassen, wusste nicht, ob Sie dort vielleicht etwas hinzuzufügen haben.

Ich denke, die Ideen hier sind ein sehr sauberes Muster für den Umgang mit externen Zuständen, obwohl ich, nachdem ich ein bisschen damit herumgespielt habe, zum HOC-Ansatz tendiere.

Auch @gaearon Ich habe Ihr HOC oben etwas vereinfacht, indem ich ein statisches - ComposedComponent.observe und this.state anstelle von this.state.data - https://gist.github.com/tgriesser/d5d80ade6f895c28e659 verwendet habe

Es sieht wirklich gut aus mit den vorgeschlagenen es7-Dekoratoren:

<strong i="20">@observing</strong>
class Foo extends Component {
  static observe(props, context) {
    return {
      myContent: xhr(props.url)
    };
  }
  render() {
    var myContent = this.props.data.myContent;
    return <div>{myContent}</div>;
  }
}

Der Klassendekorateur könnte sogar so weit gehen, einen Getter für data hinzuzufügen, um es der ursprünglich vorgeschlagenen API sehr nahe zu bringen (abzüglich des lokalen Status, der sich auf beobachtbare Abonnements auswirkt, was meiner Meinung nach eine gute Sache ist - viel weniger Lärm abonnieren / abbestellen).

Das Fehlen eines synchronen Abonnements könnte ein großes Problem sein.

Ich glaube, ich habe es verstanden, "das Zustandsproblem" und das "seitliche Laden von Daten" (abgeleitete Daten) auf konsistente Weise zu lösen. Es macht es in einem zustandslosen "React-way". Ich habe einen Weg gefunden, wie ich die Zustandskonsistenz zu jedem Zeitpunkt aufrechterhalten kann, und es passt zum Muster UI = React(state) . Es ist unwahrscheinlich, dass ich keine Hände mehr habe, um es absolut kugelsicher zu machen, weitere Beispiele hinzuzufügen und eine gute Präsentation zu machen. https://github.com/AlexeyFrolov/slt . Andererseits ist es gut getestet und ich verwende es iterativ in meinen Produktionsprojekten. Kluge Köpfe sind herzlich willkommen.

Hallo zusammen,

Es ist lustig, dass ich vorher nicht in diesen Thread gestolpert bin, da wir in unserer Firma vor einem halben Jahr auf die gleichen Probleme gestoßen sind, die in diesem Vorschlag angesprochen wurden.
Wir haben angefangen, React für ein großes Projekt zu verwenden (denken Sie an einen Editor mit der Komplexität von Microsoft Visio, mit sehr zyklischen Daten). Vanilla React Count kann nicht mit unseren Leistungsanforderungen mithalten,
und flux war für uns aufgrund der großen Menge an Boilerplates und der Fehleranfälligkeit aller Abonnements ein No-Go. Also fanden wir heraus, dass wir auch beobachtbare Datenstrukturen brauchten.

Da wir nichts gebrauchsfertiges finden konnten, haben wir unsere eigene Observables-Bibliothek erstellt, basierend auf den Prinzipien der Knockout-Observables (insbesondere: automatische Abonnements).
Dies passt eigentlich sehr gut in den aktuellen Lebenszyklus von React-Komponenten, und wir brauchten keine seltsamen Hacks oder gar Child-Rendering-Callbacks (das unten verwendete ObserverMixin ist etwa 10 loc).
Dies hat unsere DX sehr verbessert und für unser Team so hervorragend funktioniert, dass wir uns entschieden haben, sie als Open Source zu veröffentlichen . Inzwischen ist es ziemlich kampferprobt (es bietet zum Beispiel ein ES7 beobachtbares Array-Polyfill) und hochgradig optimiert.
Hier ist ein kurzes Timer-Beispiel (auch als JSFiddle verfügbar), IMHO könnte der DX nicht viel besser sein ... :erleichtert:

var store = {};
// add observable properties to the store
mobservable.props(store, {
    timer: 0
});

// of course, this could be put flux-style in dispatchable actions, but this is just to demo Model -> View
function resetTimer() {
    store.timer = 0;
}

setInterval(function() {
    store.timer += 1;
}, 1000);

var TimerView = React.createClass({
    // This component is actually an observer of all store properties that are accessed during the last rendering
    // so there is no need to declare any data use, nor is there (seemingly) any state in this component
    // the combination of mobservable.props and ObserverMixin does all the magic for us.
    // UI updates are nowhere forced, but all views (un)subscribe to their data automatically
    mixins: [mobservable.ObserverMixin],

    render: function() {
        return (<span>Seconds passed: {this.props.store.timer}</span>);
    }
});

var TimerApp = React.createClass({
    render: function() {
        var now = new Date(); // just to demonstrate that TimerView updates independently of TimerApp
        return (<div>
            <div>Started rendering at: {now.toString()}</div>
            <TimerView {...this.props} />
            <br/><button onClick={resetTimer}>Reset timer</button>
        </div>);
    }
});

// pass in the store to the component tree (you could also access it directly through global vars, whatever suits your style)
React.render(<TimerApp store={store} />, document.body);

Weitere Einzelheiten zu diesem Ansatz finden Sie in diesem Blog . Übrigens werde ich sicherstellen, dass ein Decorator und/oder Container zur lib hinzugefügt wird, für diejenigen, die ES6-Klassen verwenden.

Leider habe ich diesen Thread vor React-Europe nicht gesehen, sonst hätten wir eine schöne Gelegenheit gehabt, React & Observables zu diskutieren. Aber vielen Dank für die inspirierenden Gespräche! :+1: Ich mochte besonders die Abstraktionen von GraphQL und die Gedankenarbeit hinter Redux :)

@mweststrate Ich bin der Meinung, dass die Community letztendlich zwischen den Lösungen "Observables" und "Unveränderliche Daten" wählen muss. Vielleicht müssen wir auf irgendeine Weise mischen, um die Stärken beider Ansätze in einer Lösung zu haben (https://github.com/AlexeyFrolov/slt/issues/4). In meiner Lösung habe ich den „Immutable Data“-Ansatz mit Fokus auf die Konsistenz des Zustands zu jedem Zeitpunkt implementiert. Ich plane, auch Observables und Generators zu unterstützen. Dies ist ein Beispiel für Regeln, die verwendet werden, um „abgeleitete“ oder „ergänzende“ Daten (z. B. Seitentext, Assets, Empfehlungen, Kommentare, Likes) abzurufen und die Konsistenz des App-Status aufrechtzuerhalten.

https://github.com/AlexeyFrolov/slt#rules -Beispiel

Es ist ein komplexes Beispiel aus der realen Welt (mein Produktionscode), das zeigt, wie API-Umleitungen mit einem Location-Header gehandhabt werden.

import r from "superagent-bluebird-promise";
import router from "./router";

export default {
    "request": function (req)  {
        let route = router.match(req.url);
        let session = req.session;
        route.url = req.url;
        return this
            .set("route", route)
            .set("session", req.session);
    },
    "route": {
        deps: ["request"],
        set: function (route, request) {
            let {name, params: { id }} = route;
            if (name === "login") {
                return this;
            }
            let url = router.url({name, params: {id}});
            let method = request.method ? request.method.toLowerCase() : "get";
            let req = r[method]("http://example.com/api/" + url);
            if (~["post", "put"].indexOf(method)) {
                req.send(request.body);
            }
            return req.then((resp) => {
                let ctx = this.ctx;
                let path = url.substr(1).replace("/", ".");
                if (!resp.body) {
                    let location = resp.headers.location;
                    if (location) {
                        ctx.set("request", {
                            method: "GET",
                            url: location.replace('/api', '')
                        });
                    }
                } else {
                    ctx.set(path, resp.body);
                }
                return ctx.commit();
            });
        }
    }
}

Im Übrigen ist es der gleiche Ansatz wie bei Ihnen, außer dass keine direkte Bindung an React besteht (in meinem Fall nicht erforderlich). Ich glaube, wir müssen uns irgendwie zusammenschließen, um das gemeinsame Ziel zu erreichen.

Ich denke, es ist eine schlechte Idee, weil es viele Grenzfälle geben wird, die so schwer zu berücksichtigen sind.

Aus den obigen Kommentaren kann ich mögliche Bereiche auflisten, an die der Endbenutzer jedes Mal denken muss, wenn er eine "datenreiche" Komponente erstellen möchte:

  • Serverseitiges Rendern
  • Initialisierung von .state und .data
  • .context , .props , .state und .observe Verschränkung
  • Asynchrones Rendern

Ich denke, dieser Vorschlag wird zu fehleranfälligen, instabilen und schwer zu debuggenden Komponenten führen.

Ich stimme den von @glenjamin vorgeschlagenen Lifecycle-Hooks connect und disconnect .

Entschuldigung, wenn dies nicht zum Thema gehört (ich kann es nicht genau sagen). Aber hier ist ein Beispiel dafür, warum ich denke, dass das Offenlegen der API für die Interaktion mit dem Komponentenbaum eine interessante Möglichkeit wäre, dieses Problem anzugehen :

Diese Methode ist meine Hackerei, um durch den Baum zu gehen, und daher ist meine Frage, wie eine öffentliche API für diese Art von Dingen hier Innovationen beim „seitlichen Laden von Daten“ ermöglichen würde, was meiner Meinung nach auf „einen Renderer, der dies tut“ hinausläuft nicht von oben nach unten arbeiten": https://github.com/kevinrobinson/redux/blob/feature/loggit-todomvc/examples/loggit-todomvc/loggit/react_interpreter.js#L8

Aber hier ist ein Beispiel dafür, warum ich denke, dass das Offenlegen der API für die Interaktion mit dem Komponentenbaum eine interessante Möglichkeit wäre, dieses Problem anzugehen.

Ich glaube, @swannodette hat auf der ReactConf darüber gesprochen. Ich frage mich, ob Om Next es tut?

Ja, er hat das Gleiche auf der ersten ReactConf vorgeschlagen. Ich habe das neueste Om.next nicht gesehen oder den EuroClojure-Vortrag gehört, aber früher verwendete Om seine eigene Struktur, auf der die Benutzer aufbauen mussten, um dies zu tun.

Diese Methode ist meine Hackerei, um durch den Baum zu gehen, und daher ist meine Frage, wie eine öffentliche API für diese Art von Dingen hier Innovationen beim „seitlichen Laden von Daten“ ermöglichen würde, was meiner Meinung nach auf „einen Renderer, der dies tut“ hinausläuft nicht von oben nach unten arbeiten"

Ich denke, dies entspricht ungefähr dem flachen Rendering, das in Test-Utils zu finden ist - ich habe erwähnt, dass dies eine erstklassige API als Peer für DOM- und String-Rendering für @sebmarkbage bei ReactEurope ist - die Änderungen von 0.14 scheinen den Weg dafür gut zu ebnen.

Die anfängliche Reaktion ist, dass es ein bisschen niedrig sein könnte - aber ähnlich wie bei den erweiterbaren Web-Sachen denke ich, dass dies das Experimentieren im Benutzerbereich erleichtern würde.

Ich stimme zu, wenn wir eine Möglichkeit haben, den Baum zu rendern, können wir ihn durchlaufen und alle erforderlichen Daten finden und weitergeben.

Der Zugriff auf den gesamten virtuellen DOM-Baum ist eine erstaunlich leistungsstarke und nützliche Funktion, auf die ich gerne zugreifen würde, auch wenn dies als völlig separates Problem behandelt wird.
Ich denke, es macht eine Menge Sinn, wenn man bedenkt, welchen Weg React mit 0.14 einschlägt.

Angesichts der Komplexität von Observables schlage ich den netten Herren in diesem Thread demütig vor, sich Meteors Implementierung reaktiver Daten anzusehen: https://github.com/meteor/meteor/wiki/Tracker-Manual. Der Abgleich mit React erfordert buchstäblich 3-5 Codezeilen in der Methode componentWillMount , etwa so:

  componentWillMount() {
    if (typeof this.getState === 'function') {
      Tracker.autorun(() => {
        // Assuming this.getState() calls some functions that return
        // reactive data sources
        this.setState(this.getState());
      });
    }
  }

Ich weiß nicht, ob Tracker (das leicht als separate Bibliothek extrahiert werden kann) die Notwendigkeit einer beobachtbaren Unterstützung in React vermeidet, aber es scheint auf jeden Fall so zu sein.

MOBservable folgt einem sehr ähnlichen Muster, um eine Komponente seitwärts zu aktualisieren, sobald einige beobachtbare Werte geändert wurden. Es scheint also, dass die aktuellen Lebenszyklusmethoden + Dekoratoren genügend Flexibilität für Bibliotheken von Drittanbietern bieten, um diese Art von Mustern auszudrücken, und imho ein drittes Datenquellenkonzept würde die Sache nur zu sehr verkomplizieren.

    componentWillMount: function() {
        var baseRender = this.render;
        this.render = function() {
            if (this._watchDisposer)
                this._watchDisposer();
            var[rendering, disposer] = mobservableStatic.watch(() => baseRender.call(this), () => {
                    this.forceUpdate();
            });
            this._watchDisposer = disposer;
            return rendering;
        }
    },

@Mitranim Ich stimme zu, das ist eine wirklich gute Lektüre, danke, dass du das gefunden hast! Es ist effektiv das, was https://github.com/facebook/react/pull/3920 vorschlägt.

Wir sind unentschlossen, welcher Vorschlag besser ist. Nachdem ich mit beiden gespielt habe, bin ich größtenteils davon überzeugt, dass reaktive Programmierung (wie Sie verlinkt haben; https://github.com/meteor/meteor/wiki/Tracker-Manual) der richtige Weg ist, aber wir haben keinen Konsens erzielt und Wir versuchen immer noch herauszufinden, was am sinnvollsten ist, daher ist Feedback willkommen.

@Mitranim @jimfb Ich bin seit einigen Jahren ein großer Meteor-Fan. Tracker ist wirklich cool und sehr faszinierend. Ich habe eine Präsentation erstellt, die zeigt, wie eine sehr einfache Version von Tracker funktioniert:

https://github.com/ccorcos/meteor-track/blob/master/client/main.js

Und ich habe mit Tracker sogar eine beobachtbare Streams-Bibliothek erstellt:

https://github.com/ccorcos/meteor-tracker-streams

Aber als ich vollständig auf die Verwendung von React anstelle von Blaze umgestiegen bin, habe ich festgestellt, dass Tracker einfach zu kompliziert ist und manchmal eine einfache Publish/Subsrcribe/onChange-Methode nur 100x einfacher ist.

Außerdem hat Tracker für alle Fans der funktionalen Programmierung einige Nebenwirkungen, die in 99% der Fälle sinnvoll sind, aber manchmal erwischen sie Sie. So würde es in einer React-Komponente aussehen

componentWillMount: ->
  <strong i="15">@c</strong> = Tracker.autorun =>
    @setState({loading: true})
    Meteor.subscribe 'users', => @setState({loading: false})
    Tracker.autorun =>
      @setState({users: Users.find({}, {sort:{name:-1}}).fetch()})
componentWillUnmount: ->
  @c.stop()

Mein Punkt zu Nebenwirkungen ist, dass c.stop() das Abonnement und auch den inneren Autorun stoppt.

@ccorcos Ja, in https://github.com/facebook/react/pull/3920 wären alle Nebenwirkungen vollständig intern von React. Tatsächlich könnte der React-Kern sie auf eine vollständig unveränderliche/funktionale Weise implementieren, die keine Mutationen durchführen würde. Aber das ist ein Implementierungsdetail, da die Seiteneffekte sowieso nicht von außen sichtbar wären.

Interessant ... Warum also nicht einfach onChange-Callbacks verwenden? Ich habe festgestellt, dass diese am kompatibelsten sind. Unabhängig davon, ob Sie Meteor (Tracker), RxJS, Highland.js usw. verwenden, können Sie sie immer trivial mit einem Ereignisrückruf integrieren. Und dasselbe gilt für eine React-Komponente höherer Ordnung.

Was ich an Redux mag, ist, dass es diese Logik aus dem Framework heraushält und React-Komponenten als reine Funktionen behält.

@ccorcos Das Hauptproblem besteht darin, dass alle "Abonnements" bereinigt werden müssen, wenn eine Komponenteninstanz zerstört wird. Komponentenautoren vergessen diese Bereinigung oft, wodurch ein Speicherleck entsteht. Wir möchten, dass es automatisch ist, was das Schreiben erleichtert (weniger Boilerplate) und weniger fehleranfällig (Bereinigung erfolgt automatisch).

@jimfb Genau. Die automatische Abmeldung ist eines der wirklich netten Dinge am Ansatz von Tracker. Jeder Aufruf an die reaktive Datenquelle stellt das Abonnement wieder her, und sobald die Komponente aufhört, Daten abzurufen, gibt es nichts zu bereinigen!

Ja, aber es wird nicht wirklich "automatisch" abgemeldet. Sie müssen c.stop() in der Komponente aufrufen, um die Bereitstellung aufzuheben. Sie können das jedoch mit einem Mixin abstrahieren.

componentWillMount: ->
  <strong i="7">@autorun</strong> =>
    @setState({loading: true})
    Meteor.subscribe 'users', => @setState({loading: false})
    Tracker.autorun =>
      @setState({users: Users.find({}, {sort:{name:-1}}).fetch()})

Aber das ist wirklich nicht anders als jede andere API. Sie können eine eingebaute Methode erstellen, die sich beim Unmounten usw. automatisch für Sie abmeldet. Sehen Sie, ich bin ein großer Meteor-Fan. Also ich will dir das nicht ausreden. Es ist nur so, dass ich manchmal finde, dass es wirklich schwierig ist, dieses Zeug jemandem zu erklären, der damit nicht vertraut ist. In der Zwischenzeit ist die Verwendung einfacher Listener / Event-Emitter viel einfacher zu verstehen und zu implementieren, und sie sind in der Regel sehr kompatibel mit dem Reaktivitätssystem, das Sie verwenden möchten ...

@ccorcos Wir sprechen möglicherweise über verschiedene Mechanismen. Als ich das letzte Mal nachgesehen habe, hatte Tracker keine dauerhaften Abonnements; Jede Funktion, die eine Abhängigkeit von einer reaktiven Datenquelle herstellt (indem sie darauf zugreift), wird _einmal_ erneut ausgeführt, wenn sie sich ändert, und das ist das Ende des Abonnements. Wenn es erneut auf die Datenquelle zugreift, stellt dies das "Abonnement" für eine weitere Änderung wieder her. Und so weiter.

@Mitranim ist richtig, die Semantik von #3920 ist "automatischer" als Meteor (die Abmeldung erfolgt wirklich automatisch) und weitaus einfacher, da im allgemeinen Anwendungsfall buchstäblich keine API-Oberfläche vorhanden ist.

@ccorcos @Mitranim Für eine gebrauchsfertige, von Tracker / Vue.js inspirierte Bibliothek könnten Sie Mobservable ausprobieren, es beobachtet alle Daten, auf die während _render_ zugegriffen wird, und entsorgt alle Abonnements beim Unmounten (bis dahin werden die Abonnements am Leben erhalten). Wir haben es bei Mendix bisher erfolgreich auf ziemlich große Projekte angewendet. Es ist ziemlich unauffällig für Ihre Daten und schmückt vorhandene Objekte, anstatt eigene Modellobjekte bereitzustellen.

Als ich das letzte Mal nachgesehen habe, hatte Tracker keine dauerhaften Abonnements

@Mitranim- Abonnements können dauerhaft sein. Überprüfen Sie dies.

sub = Meteor.subscribe('chatrooms')
# this subscription lasts until...
sub.stop()

Jetzt gibt es einige interessante Dinge mit Tracker. Überprüfen Sie dies.

comp = Tracker.autorun ->
  Meteor.subscribe('chatrooms')
# this subscription lasts until...
comp.stop()

Das letzte Beispiel ist jedoch nicht sehr nützlich. Bis wir so etwas tun.

roomId = new ReactiveVar(1)
comp = Tracker.autorun ->
  Meteor.subscribe('messages', roomId.get())
# when I change the roomId, the autorun will re-run
roomId.set(2)
# the subscription to room 1 was stopped and now the subscription to room 2 has started
# the subscription is stopped when I call stop...
comp.stop()

Jede Funktion, die eine Abhängigkeit von einer reaktiven Datenquelle herstellt (indem sie darauf zugreift), wird einmal erneut ausgeführt, wenn sie sich ändert, und das ist das Ende des Abonnements.

Das Abonnement dauert, bis der Autorun erneut ausgeführt wird (eine reaktive Abhängigkeit geändert wird) oder die Berechnung, in der er lebt, stoppt. Beide rufen den Hook compute.onInvalidate auf.

Hier ist eine super erfundene Version, die genau dasselbe erreicht hat. Hoffentlich hilft es Ihnen zu verstehen, wie Tracker funktioniert. Und vielleicht sehen Sie auch, wie chaotisch es werden kann.

comp = Tracker.autorun ->
  Meteor.subscribe('chatrooms')

# is the same as

comp = Tracker.autorun (c) ->
  sub = null
  Tracker.nonreactive ->
    # dont let comp.stop() stop the subscription using Tracker.nonreactive
    sub = Meteor.subscribe('chatrooms')
  c.onInvalidate ->
    # stop the subscription when the computation is invalidated (re-run)
    sub.stop()
# invalidate and stop the computation
comp.stop()

@ccorcos Ich sehe jetzt die Quelle der Verwirrung; es war mein Vokabular. Als ich über Abonnements sprach, meinte ich nicht _Meteor-Abonnements_. Sie bestimmen, welche Daten vom Server zum Client übertragen werden, sind aber für die Aktualisierungen der Ansichtsebene, die Gegenstand dieser Diskussion sind, nicht relevant. Als ich _subscription_ sagte, zog ich eine Parallele zwischen traditionellen Ereignis-Listenern und der Fähigkeit von Tracker, eine Funktion erneut auszuführen, die von einer reaktiven Datenquelle abhängig ist. Im Fall von React-Komponenten wäre das eine Komponentenmethode, die Daten abruft und dann setState oder forceUpdate aufruft.

@mweststrate Danke für das Beispiel, es sieht interessant aus.

Ah ja. Sie haben also eine clevere Art, es zu tun.

componentWillMount: function() {
  this.comp = Tracker.autorun(() => {
    let sub = Meteor.subscribe('messages')
    return {
      loading: !sub.ready(),
      messages: Messages.find().fetch()
    }
  })
componentWillUnmount: function() {
  this.comp.stop()
}

Das Abonnement wird einfach jedes Mal ohne Probleme neu abonniert. sub.ready und Messages.find.fetch sind beide "reaktiv" und lösen die automatische Ausführung aus, wenn sie sich ändern. Das Coole an Tracker ist, wenn Sie anfangen, die Autoruns zu verstecken und nur in der Dokumentation haben, dass eine bestimmte Funktion in einem "reaktiven Kontext" steht.

Sie könnten dies in ein Mixin werfen

componentWillMount: function() {
  this.comp = Tracker.autorun(() => {
    return this.getReactiveData()
  })
componentWillUnmount: function() {
  this.comp.stop()
}

Und dann bleibt Ihnen diese magisch reaktive Funktion, die einfach funktioniert!

getReactiveData: function() {
  let sub = Meteor.subscribe('messages')
  return {
    loading: !sub.ready(),
    messages: Messages.find().fetch()
  }
}

Tracker ist so ziemlich cool ...

@ccorcos Es stellt sich heraus, dass ich mich bei der Verwendung von Eventing im Tracker-Stil in Bezug auf automatische Abmeldungen etwas geirrt habe. Sie müssen den Listener immer noch in componentWillUnmount stoppen, da er sonst weiterhin nach Datenquellen greift und setState aufruft (es sei denn, Sie überprüfen dies mit isMounted() das jetzt veraltet ist).

Nebenbei bemerkt, Sie können elegante Dekorateure erstellen, um Komponentenmethoden reaktiv zu machen. Hier sind einige Beispiele: [1] , [2] . Sieht aus wie das:

export class Chat extends React.Component {
  <strong i="13">@reactive</strong>
  updateState () {
    this.setState({
      auth: auth.read(),
      messages: messages.read()
    })
  }

  /* ... */
}

@Mitranim ziemlich ordentlich - ich bevorzuge jedoch Funktionen höherer Ordnung. ;)

@sebmarkbage @jimfb Ich verfolge diesen Thread und den Alt-Thread (#3858) jetzt seit einigen Monaten und bin gespannt, ob das Kernteam zu diesem Problem einen Konsens oder zumindest eine allgemeine Richtung erreicht hat.

@oztune Keine Updates; Wir haben uns auf andere Prioritäten konzentriert. Wir werden in einem der Threads posten, wenn es ein Update zu diesem Thema gibt.

Leute, ich habe eine universelle API zum Erstellen von Containern erstellt. Überprüfen Sie den React-Komposer . Damit könnten wir Container mit einer höheren Ordnungsfunktion erstellen.

import { compose } from `react-komposer`;

// Create a component to display Time
const Time = ({time}) => (<div>{time}</div>);

// Create the composer function and tell how to fetch data
const composerFunction = (props, onData) => {
    const handler = setInterval(() => {
    const time = new Date().toString();
    onData(null, {time});
  }, 1000);

  const cleanup = () => clearInterval(handler);
  return cleanup;
};

// Compose the container
const Clock = compose(composerFunction)(Time);

// Render the container
ReactDOM.render(<Clock />, document.getElementById('react-root'));

Hier ist die Live-Version: https://jsfiddle.net/arunoda/jxse2yw8/

Wir haben auch einige einfache Möglichkeiten, Container mit Promises, Rx.js Observables und With Meteor's Tracker zusammenzustellen .

Sehen Sie sich auch meinen Artikel dazu an: Lassen Sie uns einige React-Container zusammenstellen

@arunoda Wir haben am Ende etwas sehr Ähnliches gemacht. Eine Sache, die ich mich frage, ist, wie Sie verhindern, dass composerFunction bei jeder Prop-Änderung aufgerufen wird?

@oztune Eigentlich läuft es jetzt wieder. Wir verwenden dieses Lokka und Meteor. Beide haben lokale Caches und treffen den Server nicht, selbst wenn wir die composerFunction mehrmals aufrufen.

Aber ich denke, wir könnten so etwas machen:

const options =  {propsToWatch: ["postId"]};
const Clock = compose(composerFunction, options)(Time);

Irgendwelche Ideen?

@arunoda Das haben wir auch versucht, aber es führt zu einer gewissen Trennung zwischen der Aktion und ihren Abhängigkeiten. Wir machen jetzt etwas Ähnliches wie „react-async“, wo, anstatt die gegebene Aktion sofort auszuführen, composerFunction eine Funktion und einen Schlüssel zurückgeben würde. Wenn sich der Schlüssel von dem vorherigen Schlüssel unterscheidet, der von composerFunction zurückgegeben wird, wird die neue Funktion ausgeführt. Ich bin mir nicht sicher, ob dies eine Tangente von diesem Github-Thread ist, also würde ich gerne auf Twitter fortfahren (gleicher Benutzername).

@oztune Ich habe eine neue GH-Ausgabe erstellt und lass uns unseren Chat dort fortsetzen. Viel besser als Twitter, denke ich.

Warum lassen Sie JSX nicht einfach Observables direkt verstehen und rendern, damit ich Observable in Requisiten übergeben kann, z. B. this.props.todo$, und sie in JSX einbetten. Dann bräuchte ich keine API, der Rest wird außerhalb von React verwaltet und HoC wird verwendet, um Observables auszufüllen. Es sollte keine Rolle spielen, ob Requisiten einfache Daten oder eine Observable enthalten, daher werden keine speziellen this.data benötigt.

{this.props.todo$

Zusätzlich könnte React render in der Lage sein, Oservable[JSX] zu rendern, was das in den Links beschriebene Design ohne zusätzliche Bibliothek ermöglichen würde.

https://medium.com/@milankinen/containers -are-dead-long-live-observable-combinators-2cb0c1f06c96#.yxns1dqin

https://github.com/milankinen/react-combinators

Hallo,
Im Moment könnten wir problemlos zustandslose Komponenten mit rxjs-Streams verwenden.
Ich verstehe die Notwendigkeit einer anderen API nicht.
Ich habe ein Beispiel geschrieben - ein Board, über das Sie mit der Maus fahren können, und wenn es 26 erreicht, ändert es sich zum Neustart.
Ich würde gerne hören, was Sie über diesen Weg denken.
Hier ist der Code:
https://jsfiddle.net/a6ehwonv/74/

@giltig : Ich lerne in letzter Zeit auch so und mag es. Zusammen mit Cycle.js.

Ich würde es begrüßen, wenn ich Event-Handler, die auf Komponenten definiert sind, irgendwie einfach abhören könnte, ohne Betreffs für die Überbrückung übergeben zu müssen. Wenn ich das richtig verstehe, ist es genau andersherum als das, was hier vorgeschlagen wird. Wenn wir alternativ React vdom für seine synthetischen Ereignisse beobachten könnten, könnte vielleicht "refs" zum Beobachten verwendet werden, um zu vermeiden, dass CSS-Tags nur zum Beobachten vorhanden sind.

Weitere RxJs könnten Objekte in CombineLatest unterstützen, sodass wir React-Funktionskomponenten direkt verwenden könnten, um größere Komponenten zu erstellen.

const myFancyReactComponent = ({surface, number, gameover}) => (
        <div> 
          {gameover ? gameover : surface}
          {number}
        </div>
)

const LiveApp = Rx.Observable.combineLatest(
    LiveSurface, DynamicNumberView, DynamicGameOver,
    myFancyReactComponent
)

Hallo,
Der Grund, warum wir Cycle oder andere Frameworks nicht verwenden, ist, dass ich Bibliotheken Frameworks vorziehe (mehr Kontrolle für den Entwickler) und außerdem die Community-Macht von React nutzen möchte.
Mittlerweile werden so viele Render-Engines für React entwickelt und es ist schade, sie nicht zu verwenden (ReactNative, ReactDom, ReactThree etc..). Es ist ziemlich einfach, nur Rxjs zu verwenden und wie im oben gezeigten Beispiel zu reagieren.

Es hätte das Denken erleichtert, wenn Reaktionskomponenten Pojos akzeptieren könnten, solange Observables als Requisiten. Im Moment ist es nicht möglich, also habe ich oben gezeigt, wie wir uns entschieden haben.

Übrigens ist das, was Sie mit MyFancyReactComponent gemacht haben, möglich, und wir haben das in einigen Fällen auch getan, obwohl Sie das jsx auch direkt schreiben können.

In Bezug auf Themen - ich denke, es ist ein gültiger Weg, weil ich schließlich in der React-Komponente nur eine Handler-Funktion verwende, die alles sein könnte. Ich entscheide mich dafür, es mit einem Betreff zu implementieren, der Ereignisse empfängt, aber jemand anderes kann sich anders entscheiden - es ist flexibel.

Es hätte das Denken erleichtert, wenn Reaktionskomponenten Pojos akzeptieren könnten, solange Observables als Requisiten. Im Moment ist es nicht möglich, also habe ich oben gezeigt, wie wir uns entschieden haben.

beobachtbare Requisiten haben auf Dauer keinen Sinn. Es hat in diesem Zusammenhang eigentlich überhaupt keinen Sinn..

Es klingt für mich so, als hätten wir mit Suspense (Cache + Kontext) ein anderes Modell gefunden. Der Cache selbst erhält möglicherweise Unterstützung für Abonnements. Sie können die verbleibende Arbeit für Suspense unter https://github.com/facebook/react/issues/13206 verfolgen

Für Einzelfälle bieten wir auch ein Abo- Paket an.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen