Ich schreibe meine App neu, um Flux zu verwenden, und habe ein Problem beim Abrufen von Daten aus Stores. Ich habe viele Komponenten und sie nisten viel. Einige von ihnen sind groß ( Article
), andere klein und einfach ( UserAvatar
, UserLink
).
Ich habe Probleme damit, wo in der Komponentenhierarchie ich Daten aus Stores lesen sollte.
Ich habe zwei extreme Ansätze ausprobiert, von denen mir keiner gefallen hat:
Alle Entitätskomponenten lesen ihre eigenen Daten
Jede Komponente, die einige Daten aus dem Store benötigt, empfängt nur die Entitäts-ID und ruft die Entität selbst ab.
Zum Beispiel Article
ist vergangen articleId
, UserAvatar
und UserLink
weitergegeben werden userId
.
Dieser Ansatz hat mehrere wesentliche 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 möglicherweise Stores abonnieren.
- Es ist schwierig zu verfolgen, wie Daten in welcher Reihenfolge aktualisiert werden, da jede Komponente ihre Daten unabhängig abruft.
- Auch wenn sich möglicherweise bereits eine Entität im Status befindet, müssen Sie ihre ID an untergeordnete Elemente weitergeben, die sie erneut abrufen (oder die Konsistenz beeinträchtigen).
Alle Daten werden einmal auf der obersten Ebene gelesen und an die Komponenten weitergegeben
Als ich es satt hatte, Fehler aufzuspüren, habe ich versucht, alle Daten, die abgerufen wurden, auf die oberste Ebene zu bringen. Dies erwies sich jedoch als unmöglich, da ich für einige Entitäten mehrere Verschachtelungsebenen habe.
Beispielsweise:
- A
Category
enthältUserAvatar
s von Personen, die zu dieser Kategorie beitragen; - Ein
Article
kann mehrereCategory
s haben.
Wenn ich also alle Daten aus Stores auf der Ebene von abrufen Article
möchte, muss ich:
- Artikel abrufen von
ArticleStore
; - Rufen Sie alle Artikelkategorien von ab
CategoryStore
. - Rufen Sie die Mitwirkenden jeder Kategorie separat ab
UserStore
. - Übergeben Sie all diese Daten irgendwie an Komponenten.
Noch frustrierender ist, dass ich, wenn ich eine tief verschachtelte Entität benötige, jeder Verschachtelungsebene Code hinzufügen muss, um sie zusätzlich weiterzugeben.
Zusammenfassen
Beide Ansätze scheinen fehlerhaft zu sein. Wie löse ich dieses Problem am elegantesten?
Meine Ziele:
Geschäfte sollten nicht wahnsinnig viele Abonnenten haben. Es ist dumm
UserLink
,UserStore
wenn jeder zuhört, wenn übergeordnete 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 (z. B. Kategorie erhält eine
curator
).
quelle
Antworten:
Die meisten Benutzer beginnen mit dem Abhören der relevanten Speicher in einer Controller-Ansichtskomponente am oberen Rand der Hierarchie.
Später, wenn es so aussieht, als würden viele irrelevante Requisiten durch die Hierarchie an eine tief verschachtelte Komponente weitergegeben, einige Leute entscheiden, dass es eine gute Idee ist, eine tiefere Komponente auf Änderungen in den Läden warten zu lassen. Dies bietet eine bessere Kapselung der Problemdomäne, um die es in diesem tieferen Zweig des Komponentenbaums geht. Dafür gibt es gute Argumente.
Ich höre jedoch lieber immer oben zu und gebe einfach alle Daten weiter. Manchmal nehme ich sogar den gesamten Status des Geschäfts und übergebe ihn als einzelnes Objekt durch die Hierarchie. Dies mache ich für mehrere Geschäfte. Also hätte ich eine Requisite für den
ArticleStore
Staat und eine andere für dieUserStore
Status usw. Ich finde, dass das Vermeiden tief verschachtelter Controller-Ansichten einen singulären Einstiegspunkt für die Daten beibehält und den Datenfluss vereinheitlicht. Andernfalls habe ich mehrere Datenquellen, und das Debuggen kann schwierig werden.Die Typprüfung ist mit dieser Strategie schwieriger, aber Sie können mit den PropTypes von React eine "Form" oder eine Typvorlage für das große Objekt als Requisite 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 -Validierung
Beachten Sie, dass Sie möglicherweise die Logik der Zuordnung von Daten zwischen Filialen in die Filialen selbst einfügen möchten. Sie
ArticleStore
können alsowaitFor()
dieUserStore
relevanten Benutzer in jedenArticle
Datensatz einbeziehen, den sie bereitstellengetArticles()
. Wenn Sie dies in Ihren Ansichten tun, wird die Logik in die Ansichtsebene verschoben. Dies sollten Sie nach Möglichkeit vermeiden.Sie könnten auch versucht sein, dies zu verwenden
transferPropsTo()
, und viele Leute tun dies gerne, aber ich bevorzuge es, alles explizit zu halten, um die Lesbarkeit und damit die Wartbarkeit zu gewährleisten.FWIW, ich verstehe, dass David Nolen mit seinem Om-Framework (das etwas Flux-kompatibel ist ) einen ähnlichen Ansatz mit einem einzigen Dateneintrittspunkt auf dem Stammknoten verfolgt - das Äquivalent in Flux wäre, nur eine Controller-Ansicht zu haben alle Geschäfte hören. Dies wird effizient gemacht, indem
shouldComponentUpdate()
unveränderliche Datenstrukturen verwendet werden, die als Referenz mit === verglichen werden können. Informationen zu unveränderlichen Datenstrukturen finden Sie in Davids Mori oder in Facebooks unveränderlichen Daten . Mein begrenztes Wissen über Om stammt hauptsächlich aus der Zukunft von JavaScript MVC Frameworksquelle
Der Ansatz, zu dem ich gekommen bin, besteht darin, dass jede Komponente ihre Daten (nicht IDs) als Requisite erhält. Wenn eine verschachtelte Komponente eine verwandte Entität benötigt, muss sie von der übergeordneten Komponente abgerufen werden.
In unserem Beispiel
Article
sollte einearticle
Requisite ein Objekt sein (vermutlich vonArticleList
oder abgerufenArticlePage
).Denn
Article
auch machen will ,UserLink
undUserAvatar
für Artikel des Autors, wird es abonniertUserStore
und haltenauthor: UserStore.get(article.authorId)
in seinem Zustand. Es wird dann gerendertUserLink
undUserAvatar
damitthis.state.author
. Wenn sie es weitergeben wollen, können sie es. Keine untergeordneten Komponenten müssen diesen Benutzer erneut abrufen.Wiederholen:
Dies löst mein Problem ganz gut. Codebeispiel neu geschrieben, um diesen Ansatz zu verwenden:
Dies hält die innersten Komponenten dumm, zwingt uns aber nicht dazu, die Komponenten der obersten Ebene zum Teufel zu machen.
quelle
Meine Lösung ist viel einfacher. Jede Komponente, die ihren eigenen Status hat, darf mit Geschäften sprechen und diese abhören. Dies sind sehr Controller-ähnliche Komponenten. Tiefere verschachtelte Komponenten, die den Status nicht beibehalten, sondern nur Inhalte rendern, sind nicht zulässig. Sie erhalten nur Requisiten für reines Rendering, sehr anschaulich.
Auf diese Weise fließt alles von zustandsbehafteten Komponenten in zustandslose Komponenten. Die Anzahl der Statefuls niedrig halten.
In Ihrem Fall wäre Article zustandsbehaftet und daher würden Gespräche mit den Stores und UserLink usw. nur gerendert, sodass article.user als Requisite empfangen würde.
quelle
Die in Ihren beiden Philosophien beschriebenen Probleme treten bei jeder einzelnen Seitenanwendung auf.
Sie werden in diesem Video kurz erläutert: https://www.youtube.com/watch?v=IrgHurBjQbg und Relay ( https://facebook.github.io/relay ) wurden von Facebook entwickelt, um den von Ihnen beschriebenen Kompromiss zu überwinden.
Der Ansatz von Relay ist sehr datenorientiert. Es ist eine Antwort auf die Frage "Wie erhalte ich nur die erforderlichen Daten für jede Komponente in dieser Ansicht in einer Abfrage an den Server?" Gleichzeitig stellt Relay sicher, dass der Code nur wenig gekoppelt ist, wenn eine Komponente in mehreren Ansichten verwendet wird.
Wenn Relay keine Option ist , scheint mir "Alle Entitätskomponenten lesen ihre eigenen Daten" für die von Ihnen beschriebene Situation ein besserer Ansatz zu sein. Ich denke, das Missverständnis in Flux ist, was ein Geschäft ist. Das Konzept des Geschäfts existiert nicht als Ort, an dem ein Modell oder eine Sammlung von Objekten aufbewahrt wird. Speicher sind temporäre Orte, an denen Ihre Anwendung die Daten ablegt, bevor die Ansicht gerendert wird. Der wahre Grund, warum sie existieren, besteht darin, das Problem der Abhängigkeiten zwischen den Daten zu lösen, die in verschiedenen Speichern gespeichert sind.
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 einen Flussmittelspeicher tatsächlich zu einem Ort, an dem eine Sammlung von Objekten eines bestimmten Typs abgelegt werden kann, der nicht für die gesamte Zeit bündig ist, in der der Benutzer den Browser geöffnet hält, aber nach meinem Verständnis von Flussmitteln ist dies kein Speicher sollte sein.
Die Lösung besteht darin, eine weitere Ebene zu haben, auf der Sie die zum Rendern Ihrer Ansicht erforderlichen Entitäten (und möglicherweise weitere) speichern und auf dem neuesten Stand halten. Wenn Sie diese Ebene, die Modelle und Sammlungen abstrahiert, ist es kein Problem, wenn Sie die Unterkomponenten erneut abfragen müssen, um ihre eigenen Daten zu erhalten.
quelle