javascript vorhang Auf welcher Verschachtelungsebene sollten Komponenten Entitäten von Stores in Flux lesen?



vorhang länge bestimmen (4)

Ich überschreibe meine App, um Flux zu verwenden, und ich habe ein Problem beim Abrufen von Daten aus Stores. Ich habe viele Komponenten und sie nisten sehr. Einige von ihnen sind groß ( Article ), einige sind klein und einfach ( UserAvatar , UserLink ).

Ich habe damit zu kämpfen, wo ich in der Komponentenhierarchie Daten aus Stores lesen sollte.
Ich habe zwei extreme Ansätze ausprobiert, von denen mir keiner wirklich gefallen hat:

Alle Entitätskomponenten lesen ihre eigenen Daten

Jede Komponente, die einige Daten von Store benötigt, empfängt nur die Entitäts-ID und ruft die Entität selbstständig ab.
Beispiel: Article wird an articleId , UserAvatar und UserLink werden an userId .

Dieser Ansatz hat mehrere erhebliche Nachteile (siehe Codebeispiel).

var Article = React.createClass({
  mixins: [createStoreMixin(ArticleStore)],

  propTypes: {
    articleId: PropTypes.number.isRequired
  },

  getStateFromStores() {
    return {
      article: ArticleStore.get(this.props.articleId);
    }
  },

  render() {
    var article = this.state.article,
        userId = article.userId;

    return (
      <div>
        <UserLink userId={userId}>
          <UserAvatar userId={userId} />
        </UserLink>

        <h1>{article.title}</h1>
        <p>{article.text}</p>

        <p>Read more by <UserLink userId={userId} />.</p>
      </div>
    )
  }
});

var UserAvatar = React.createClass({
  mixins: [createStoreMixin(UserStore)],

  propTypes: {
    userId: PropTypes.number.isRequired
  },

  getStateFromStores() {
    return {
      user: UserStore.get(this.props.userId);
    }
  },

  render() {
    var user = this.state.user;

    return (
      <img src={user.thumbnailUrl} />
    )
  }
});

var UserLink = React.createClass({
  mixins: [createStoreMixin(UserStore)],

  propTypes: {
    userId: PropTypes.number.isRequired
  },

  getStateFromStores() {
    return {
      user: UserStore.get(this.props.userId);
    }
  },

  render() {
    var user = this.state.user;

    return (
      <Link to='user' params={{ userId: this.props.userId }}>
        {this.props.children || user.name}
      </Link>
    )
  }
});

Nachteile dieses Ansatzes:

  • Es ist frustrierend, dass 100s Komponenten potenziell Stores abonnieren;
  • Es ist schwer zu verfolgen, wie Daten in welcher Reihenfolge aktualisiert werden, da jede Komponente ihre Daten unabhängig voneinander abruft.
  • Auch wenn Sie möglicherweise bereits eine Entität im Status haben, müssen Sie ihre ID an untergeordnete Elemente übergeben, die sie erneut abrufen (oder die Konsistenz unterbrechen).

Alle Daten werden einmal auf oberster Ebene gelesen und an Komponenten weitergegeben

Als ich es leid war, Fehler zu finden, habe ich versucht, alle Daten auf die oberste Ebene zu bringen. Dies erwies sich jedoch als unmöglich, da ich für einige Entitäten mehrere Verschachtelungsebenen habe.

Beispielsweise:

  • Eine Category enthält UserAvatar s von Personen, die zu dieser Kategorie beitragen.
  • Ein Article kann mehrere Category .

Wenn ich also alle Daten aus den Stores auf der Ebene eines Article abrufen möchte, müsste ich:

  • Artikel aus ArticleStore
  • Abrufen aller Artikelkategorien aus CategoryStore ;
  • Die einzelnen Mitwirkenden der jeweiligen Kategorie UserStore aus UserStore ;
  • Irgendwie geben Sie all diese Daten an die Komponenten weiter.

Noch frustrierender ist es, wenn ich eine tief verschachtelte Entität benötige, müsste ich Code zu jeder Verschachtelungsebene hinzufügen, um sie zusätzlich weiterzugeben.

Zusammenfassen

Beide Ansätze scheinen fehlerhaft zu sein. Wie löse ich dieses Problem am elegantesten?

Meine Ziele:

  • Stores sollten keine wahnsinnige Anzahl von Abonnenten haben. Es ist dumm für jeden UserLink zu hören, wenn Eltern-Komponenten dies bereits tun.

  • Wenn die übergeordnete Komponente ein Objekt aus dem Speicher abgerufen hat (z. B. user ), möchte ich nicht, dass verschachtelte Komponenten es erneut abrufen müssen. Ich sollte es über Requisiten weitergeben können.

  • Ich sollte nicht alle Entitäten (einschließlich Beziehungen) auf der obersten Ebene abrufen müssen, da dies das Hinzufügen oder Entfernen von Beziehungen erschweren würde. Ich möchte nicht jedes Mal neue Requisiten auf allen Verschachtelungsebenen einführen, wenn eine verschachtelte Entität eine neue Beziehung erhält (zB Kategorie erhält einen curator ).


Answer #1

Die Probleme, die in Ihren beiden Philosophien beschrieben sind, sind bei jeder einzelnen Seitenanwendung üblich.

Sie werden in diesem Video kurz erläutert: https://www.youtube.com/watch?v=IrgHurBjQbg und Relay ( https://facebook.github.io/relay ) wurde von Facebook entwickelt, um den von Ihnen beschriebenen Kompromiss zu überwinden.

Der Ansatz von Relay ist sehr datenzentriert. Es ist eine Antwort auf die Frage "Wie bekomme ich nur die benötigten Daten für jede Komponente in dieser Ansicht in einer Abfrage an den Server?" Und gleichzeitig stellt Relay sicher, dass Sie eine kleine Kopplung über den Code haben, wenn eine Komponente in mehreren Ansichten verwendet wird.

Wenn "Relay" keine Option ist , "Alle Entitätskomponenten lesen ihre eigenen Daten", erscheint mir für die von Ihnen beschriebene Situation ein besserer Ansatz. Ich denke, das Missverständnis in Flux ist, was ein Laden ist. Das Konzept des Ladens existiert nicht als der Ort, an dem ein Modell oder eine Sammlung von Objekten aufbewahrt wird. Speicher sind temporäre Speicherorte, an denen Ihre Anwendung die Daten vor dem Rendern der Ansicht ablegt. Der wirkliche Grund, warum sie existieren, besteht darin, das Problem der Abhängigkeiten zwischen den Daten zu lösen, die in verschiedene Geschäfte gelangen.

Was Flux nicht spezifiziert, ist, wie sich ein Geschäft auf das Konzept von Modellen und die Sammlung von Objekten bezieht (a la Backbone). In diesem Sinne machen einige Leute tatsächlich einen Flux Store zu einem Ort, an dem die Sammlung von Objekten eines bestimmten Typs, die nicht flush ist, während der Benutzer den Browser offen hält, aber, wie ich Flux verstehe, ist das nicht, was ein Geschäft sollte sein.

Die Lösung besteht darin, eine weitere Ebene zu haben, auf der die Entitäten gespeichert werden, die zum Rendern Ihrer Ansicht (und möglicherweise auch mehr) erforderlich sind, und werden auf dem neuesten Stand gehalten. Wenn Sie diese Schicht, die Modelle und Sammlungen abstrahiert, ist es kein Problem, wenn Sie die Unterkomponenten erneut abfragen müssen, um ihre eigenen Daten zu erhalten.


Answer #2

Die meisten Leute beginnen damit, die relevanten Speicher in einer Controller-View-Komponente nahe der Spitze der Hierarchie anzuhören.

Später, wenn es so aussieht, als ob eine Menge irrelevanter Requisiten durch die Hierarchie zu einer tief verschachtelten Komponente weitergegeben werden, werden einige Leute entscheiden, dass es eine gute Idee ist, eine tiefere Komponente auf Veränderungen in den Läden lauschen zu lassen. Dies bietet eine bessere Einkapselung der Problemdomäne, um die es in diesem tieferen Zweig des Komponentenbaums geht. Es gibt gute Argumente dafür, dies vernünftig zu tun.

Ich höre jedoch lieber immer oben zu und gebe einfach alle Daten weiter. Manchmal nehme ich sogar den gesamten Zustand des Ladens und gebe ihn als einzelnes Objekt durch die Hierarchie und mache das für mehrere Geschäfte. Ich hätte also eine Requisite für den ArticleStore und eine weitere für den UserStore usw. Ich finde, dass das Vermeiden von tief verschachtelten Controller-Ansichten einen singulären Einstiegspunkt für die Daten UserStore und den Datenfluss vereinheitlicht. Andernfalls habe ich mehrere Datenquellen, und das kann schwierig zu debuggen sein.

Die Typüberprüfung ist mit dieser Strategie schwieriger, aber Sie können eine "Form" oder eine Typvorlage für das Objekt mit großem Objekt als Prop mit React's PropTypes einrichten. Siehe: https://github.com/facebook/react/blob/master/src/core/ReactPropTypes.js#L76-L91 http://facebook.github.io/react/docs/reusable-components.html#prop-validation

Beachten Sie, dass Sie möglicherweise die Logik der Zuordnung von Daten zwischen Stores in den Stores selbst festlegen möchten. So könnte Ihr ArticleStore den ArticleStore waitFor() und die relevanten Benutzer mit jedem Article Datensatz UserStore , der durch getArticles() . Wenn Sie dies in Ihren Ansichten tun, klingt das so, als würden Sie Logik in die Ansichtsebene schieben, was Sie möglichst vermeiden sollten.

Sie könnten auch versucht sein, transferPropsTo() , und viele Leute mögen es, dies zu tun, aber ich bevorzuge es, alles für die Lesbarkeit und damit Wartbarkeit explizit zu halten.

FWIW, ich gehe davon aus, dass David Nolen einen ähnlichen Ansatz mit seinem Om-Framework (das etwas Flux-kompatibel ist ) mit einem einzigen Eingangspunkt von Daten auf dem Root-Knoten verwendet - das Äquivalent in Flux wäre, nur eine Controller-Ansicht zu haben auf alle Geschäfte hören. Dies wird durch den Einsatz von shouldComponentUpdate() und unveränderlichen Datenstrukturen, die als Referenz mit === verglichen werden können, effizient gemacht. Für unveränderliche Datenstrukturen immutable-js Davids mori oder Facebooks immutable-js . Mein begrenztes Wissen über Om kommt hauptsächlich von der Zukunft von JavaScript MVC Frameworks


Answer #3

Meine Lösung ist viel einfacher. Jede Komponente, die ihren eigenen Status hat, darf reden und auf Speicher hören. Dies sind sehr Controller-ähnliche Komponenten. Tiefere verschachtelte Komponenten, die den Zustand nicht beibehalten, sondern nur Inhalte rendern, sind nicht erlaubt. Sie erhalten nur Requisiten für reines Rendering, sehr anschaulich.

So fließt alles von zustandsbehafteten Komponenten in zustandslose Komponenten. Die Anzahl der Statefuls niedrig halten.

In deinem Fall wäre der Artikel statusbehaftet und würde daher mit den Stores kommunizieren und UserLink etc. würde nur so rendern, dass er article.user als Props erhalten würde.


Answer #4

Der Ansatz, bei dem ich ankam, ist, dass jede Komponente ihre Daten (keine IDs) als Props erhält. Wenn eine verschachtelte Komponente eine verknüpfte Entität benötigt, muss sie von der übergeordneten Komponente abgerufen werden.

In unserem Beispiel sollte Article eine article die ein Objekt ist (vermutlich von ArticleList oder ArticleList abgerufen).

Da Article UserLink und UserAvatar für den Autor des Artikels rendern UserLink , wird es UserStore abonnieren und den author: UserStore.get(article.authorId) in seinem Zustand behalten. Es wird dann UserLink und UserAvatar mit diesem this.state.author . Wenn sie es weiter weitergeben wollen, können sie es tun. Keine untergeordneten Komponenten müssen diesen Benutzer erneut abrufen.

Wiederholen:

  • Keine Komponente erhält je eine ID als Prop; Alle Komponenten erhalten ihre jeweiligen Objekte.
  • Wenn untergeordnete Komponenten eine Entität benötigen, liegt es in der Verantwortung der Eltern, sie abzurufen und als Prop zu übergeben.

Dies löst mein Problem recht gut. Codebeispiel neu geschrieben, um diesen Ansatz zu verwenden:

var Article = React.createClass({
  mixins: [createStoreMixin(UserStore)],

  propTypes: {
    article: PropTypes.object.isRequired
  },

  getStateFromStores() {
    return {
      author: UserStore.get(this.props.article.authorId);
    }
  },

  render() {
    var article = this.props.article,
        author = this.state.author;

    return (
      <div>
        <UserLink user={author}>
          <UserAvatar user={author} />
        </UserLink>

        <h1>{article.title}</h1>
        <p>{article.text}</p>

        <p>Read more by <UserLink user={author} />.</p>
      </div>
    )
  }
});

var UserAvatar = React.createClass({
  propTypes: {
    user: PropTypes.object.isRequired
  },

  render() {
    var user = this.props.user;

    return (
      <img src={user.thumbnailUrl} />
    )
  }
});

var UserLink = React.createClass({
  propTypes: {
    user: PropTypes.object.isRequired
  },

  render() {
    var user = this.props.user;

    return (
      <Link to='user' params={{ userId: this.props.user.id }}>
        {this.props.children || user.name}
      </Link>
    )
  }
});

Dies hält die innersten Komponenten dumm, aber zwingt uns nicht dazu, die Komponenten der obersten Ebene zu verkomplizieren.





reactjs-flux