Willkommen, React-Liebhaber und Amateure wie ich! Ich habe heute ein Rätsel für Sie.
Nehmen wir an, Sie möchten eine Liste von Elementen in einer 2-Spalten-Struktur rendern. Jedes dieser Elemente ist eine separate Komponente. Nehmen wir zum Beispiel an, wir hätten eine Liste von Alben und möchten diese als Vollseiten-2-Spalten-Liste rendern. Jedes „Album“ ist eine React-Komponente.

Nun nehmen wir an, das CSS-Framework, das Sie verwenden, erfordert, dass Sie ein 2-Spalten-Layout wie dieses rendern…
<div class="columns">
<div class="column"> Column 1 </div>
<div class="column"> Column 2 </div>
<div class="columns">
Das bedeutet, um die Alben korrekt zu rendern, müssen Sie einen `columns`-Div-Tag öffnen, zwei Alben rendern, dann den Tag schließen. Das tun Sie immer wieder, bis alle Alben gerendert wurden.
Ich habe es gelöst, indem ich die Menge in Blöcke zerlegt und bei jedem zweiten Album bedingt in einer separaten Renderfunktion gerendert habe. Diese Renderfunktion wird nur für jedes zweite Element aufgerufen.
class App extends Component {
state = {albums: [] }
async componentDidMount() {
let data = Array.from(await GetAlbums());
this.setState({ albums: data } );
}
render() {
return (
<section className="section">
{this.state.albums.map((album, index) => {
// use the modulus operator to determine even items return index % 2 ?
this.renderAlbums(index) : '';
})}
</section>
)
}
renderAlbums(index) {
// two albums at a time - the current and previous item
let albums = [this.state.albums[index - 1], this.state.albums[index]];
return (
<div className="columns" key={index}>
{albums.map(album => {
return (
<Album album={album} />
);
})}
</div>
);
}
}
Vollständiges Projekt anzeigen
Eine andere Möglichkeit wäre, das Album-Array in ein zweidimensionales Array aufzuteilen und darüber zu iterieren. Der erste hervorgehobene Block unten teilt das Array auf. Der zweite ist die stark vereinfachte Rendering-Logik.
class App extends Component {
state = {albums: []}
async componentDidMount() {
let data = Array.from(await GetAlbums());
// split the original array into a collection of two item sets
data.forEach((item, index) => {
if (index % 2) {
albums.push([data[index - 1], data[index]]);
}
});
this.setState({
albums: albums
});
}
render() {
return (
<section className="section">
{this.state.albums.map((album, index) => {
return (
<div className="columns">
<Album album={album[0]}></Album>
<Album album={album[1]}></Album>
</div>
)
})}
</section>
)
}
}
Vollständiges Projekt anzeigen
Das bereinigt das JSX ziemlich, aber jetzt gebe ich die `Album`-Komponente redundant ein, was sich einfach falsch anfühlt.
Sarah Drasner wies mich darauf hin, dass ich eines der wichtigeren Szenarien hier noch nicht einmal berücksichtigt hatte, nämlich das Szenario „unbekannte Unterseite“.
Unbekannte Unterseite
Beide meiner obigen Lösungen gehen davon aus, dass der vom Fetch erhaltene Ergebnissatz final ist. Aber was ist, wenn er es nicht ist?
Was ist, wenn wir Daten von einem Server streamen (im RxJs-Stil) und nicht wissen, wie oft wir einen Ergebnissatz erhalten, und nicht wissen, wie viele Elemente in einem bestimmten Satz enthalten sein werden. Das kompliziert die Dinge erheblich und zerstört die vorgeschlagenen Lösungen vollständig. Tatsächlich können wir sagen, dass keine dieser Lösungen ideal ist, da sie nicht für diesen Anwendungsfall skalieren.
Ich denke, die absolut einfachste Lösung hier wäre, dies im CSS zu beheben. Lassen Sie das CSS sich um das Layout kümmern, wie es Gott bestimmt hat. Ich halte es immer noch für wichtig, sich anzusehen, wie man das mit JSX macht, denn es gibt Leute, die jeden Tag mit solchen Schikanen in realen Anwendungen zu tun haben. Die Anforderungen sind nicht immer das, was wir wollen.
Wie würden Sie es machen?
Meine Frage ist genau das – wie würden Sie das machen? Gibt es einen saubereren, effizienteren Weg? Wie kann das so gemacht werden, dass es mit einer unbekannten Unterseite skaliert? Neugierige Geister (insbesondere meine) würden es gerne wissen.
Ich würde das obere Div überhaupt nicht verwenden.
Ich würde nur CSS verwenden, um sie nebeneinander zu schieben.
.col {
box-sizing: border-box;
width: 50%;
}
.col:nth-child(odd) {
margin-right: 0.5rem
}
.col:nth-child(even) {
margin-left: 0.5rem;
}
.col:not(:nth-child(0)),
.col:not(:nth-child(0)) {
margin-top: 1rem;
}
das erste .col braucht ein display: inline-block; kann Kommentare nicht bearbeiten *heul*
Ich denke, das ist letztendlich die richtige Antwort. Ich habe versucht, mich in eine Situation hineinzuversetzen, in der ich das CSS nicht anfassen kann. Wenn ich das so rendern müsste – aus welchem Grund auch immer – mit Logik statt mit Layout.
Auch – das „OMG, ich kann meinen Kommentar nicht bearbeiten“-Ding hat mich auch schon erwischt.
Ich weiß, dass es vielleicht nicht optimal ist, ein ganzes Paket zu laden, aber mit React-Bootstrap können Sie deren Spalten-/Zeilenmethode problemlos verwenden, ohne all das tun zu müssen. Viel modularer auch.
Warum nicht die Flexbox-Eigenschaft `flex-flow: column wrap` für das Layout verwenden? Dies würde es Ihnen ermöglichen, Ihre Albumkomponente einfach zu wiederholen.
Ja, noch besser als meine Legacy-CSS-Lösung. Im Büro haben wir die Unterstützung für IE9 und IE10 erst vor einem Monat eingestellt. Ich muss mich immer noch daran gewöhnen, dass man dann Flexbox verwenden kann.
Ooh – nett!
Wie Samantha erwähnte, frage ich mich, wie viele Leute noch IE unterstützen. Ich habe vor ein paar Wochen eine Sitzung bei Nodevember gehalten, die Zimmer befragt und es gab eine nicht unerhebliche Anzahl von Händen, die sich hoben und sagten, sie würden *immer noch* altes IE unterstützen.
Das ist es, worauf ich immer zurückgreife. Als funktionaler Programmierer ist es einfach, fast alles mit Logik zu lösen. KISS gebietet jedoch, das Verfügbare zu nutzen, anstatt etwas Neues zu schreiben. CSS ist unglaublich leistungsfähig und in den meisten Fällen können die meisten Probleme wie diese einfach durch die Verwendung von etwas HTML und CSS gelöst werden.
Zusätzlich, wenn Sie etwas Neues wie `styled-components` verwenden, müssen Sie sich keine Gedanken über den Zugriff auf die CSS-Dateien machen, da diese in Ihren gestylten Komponenten leben. :)
Genau das. Ich wollte fragen, ob es eine strenge Anforderung für die „Zeile-zweiSpalten, Zeile-zweiSpalten“-Struktur gibt. Wenn nicht, würde ich denken, dass dies die beste Gesamtlösung ist, abgesehen von der Unterstützung älterer Browser.
+1 für Flexbox. Das war mein erster Gedanke, als ich die einleitenden Absätze las.
Wenn Sie nicht die IE-Dämonen unterstützen müssen, gibt es in diesem Fall keinen Grund, sich an ein Framework zu binden.
Ich hatte dies ursprünglich als einen Fall aufgesetzt, in dem die Änderung des CSS **nicht** möglich ist. Das lag daran, dass ich Bulma verwendete und Bulma out-of-the-box keine Multi-Line-Spalten zu unterstützen schien, selbst wenn eine Breite von 50 % (`is-half`) angegeben wurde, und ich es nicht ändern wollte.
Nachdem Samantha dies angestoßen hatte, schaute ich mir die Dokumentation erneut an und es stellte sich heraus, dass Bulma das **vollständig** kann. Alles, was Sie tun müssen, ist die Klasse `is-multiline` hinzuzufügen.
https://codepen.io/burkeholland/pen/XVrPON
RTFM, Burke!
Ich stimme Jennifer zu, dass dies mit Grid, Flex oder sogar Float erfolgen sollte. Layout ist keine JS-Sache. Ich glaube nicht, dass irgendein Framework Ihnen solche Einschränkungen auferlegen würde. Sie müssen nicht alle bereitgestellten Klassen verwenden, wenn sie nicht geeignet sind. Mich interessiert, warum Sie denken, dass es für den Empfang gestreamter Daten nicht geeignet ist. Können Sie das etwas erläutern?
Ich meinte, dass meine JSX-Rendering-Lösung nicht geeignet war. Sie operiert auf einem festen Datensatz, um zu bestimmen, wann das „columns“-Tag geöffnet und geschlossen wird. Das bedeutet, wenn Sie Streaming-Daten hätten, solange die einkommenden Sätze alle gerade sind, ist alles in Ordnung. Aber wenn Sie zum ersten Mal einen mit einer ungeraden Anzahl von Elementen haben, ist das Layout für den Rest der Seite ruiniert.
Ich bin mir nicht sicher, was Sie mit Streaming-Daten meinen. Die Renderfunktion einer Komponente hat für jeden Durchlauf Zugriff auf einen festen Datensatz. Sie generiert ein Shadow-DOM und vergleicht es mit dem, was im tatsächlichen DOM ist, dann ersetzt sie, was ersetzt werden muss.
Zum Beispiel:
– Erstes Rendering haben Sie 2 Elemente, also rendern Sie zwei Spalten
– Zweites Rendering haben Sie 3 Elemente, also rendern Sie Shadow-DOM, erkennen, dass es eine zusätzliche Zeile mit nur einer Spalte rendern muss
– Drittes Rendering haben Sie 5 Elemente, also aktualisieren Sie die zweite Zeile und rendern die dritte mit einer einzigen Spalte
Sie müssen möglicherweise die Logik von Angular nach React portieren, siehe http://rintoj.github.io/angular2-virtual-scroll/
Schöner Plugin! Obwohl React auch einen virtuellen Scroll hat, war ich mehr daran interessiert, wie ich eine unbekannte Anzahl von Elementen bedingt in ein Tag wickeln kann.
Es gibt auch `react-virtualized`, wenn Sie Virtualisierung benötigen
Da eine JS-Lösung angefordert wurde, aber ich bin am Handy, mal sehen, ob ich eine Lösung beschreiben kann.
Warum nicht einen Modulus-Operator verwenden, um das Container-Div zu schließen und für jeden geraden Listenindex einen neuen Container zu öffnen. Dann ist das Anhängen neuer Elemente an die Liste trivial, wenn ihr Index mit der ursprünglichen Liste übereinstimmt.
Ich *denke*, das mache ich bereits, es sei denn, ich lese das falsch. Das funktioniert, aber wenn sich die einzelne Liste ändert (z.B. ein Datenstrom statt eines festen Satzes), dann geht der Index verloren.
Obwohl ich zustimme, dass CSS Flexbox wahrscheinlich der Ansatz ist, den ich letztendlich verfolgen würde, frage ich mich, ob es funktionieren würde, `map` zu verwenden, um das Ende der Liste dynamisch zu gestalten?
PS: Verzeihen Sie mir etwaige Syntaxfehler (ich bin am Handy) und ich hoffe wirklich, dass der Kommentar erscheint, da ich sehe, dass das Bearbeiten eines Kommentars nicht möglich ist
Ahhh und natürlich gibt es einen Tippfehler. Im PS… `String.replace("m", "d", "come")`
Der Code schien auf meinem Handy nicht vollständig gerendert zu werden, aber er vermittelt die Idee
Ja! Dies eliminiert das zweite ``-Tag in Lösung 2. Es birgt jedoch ein neues Problem – jetzt hat die erste Iteration keine Möglichkeit, einen `key` zu definieren, weil es sich um ein 2-dimensionales Array handelt. Auch die Rendering-Syntax wirkt jetzt fast schwieriger zu lesen.
Was denkst du?
Wir haben ein ähnliches Problem in meiner Firma, für das wir eine React-Komponente veröffentlicht haben, weil ich mit den Alternativen nicht zufrieden war. Sie unterstützt Masonry-Lite-Logik mit mehreren Layouts und unendlichem Scrollen, indem sie Elemente aus dem DOM entfernt, während Sie nach unten scrollen, um den Druck auf den Browser zu verringern. Sie können sie hier in Aktion sehen
https://www.themaven.net/globallead
Scrollen Sie einfach weiter nach unten :)
Und GitHub (der öffentliche Code ist etwas veraltet, falls jemand interessiert ist, kann er mir eine E-Mail schreiben und ich veröffentliche die neueste Version aus unserem privaten Repository erneut)
https://github.com/themaven-net/cascade
Das ist SEHR interessant. Ich bemerke ein kleines Zucken während des Scrollens. Wird das durch das Masonry-Layout verursacht?
Ich würde es lieber vermeiden, das Rad neu zu erfinden und die Grid-System-Bibliothek von React-Bootstrap verwenden, um dieses Problem zu lösen. Sie bietet Optionen, mit denen Sie die Anzahl der angezeigten Spalten für große, mittlere und kleine Bildschirme festlegen können, wodurch alles nahtlos wird.
Ich bin mit React nicht vertraut, aber generell glaube ich, dass es besser ist, eine Liste (ul,ol) zu verwenden, um Elemente aufzulisten, und dann CSS zu verwenden, um das Layout nach Belieben zu ändern.
Es ist semantisch sinnvoll und einfacher zu manipulieren.
Das mag sein – ich bin definitiv nicht „Mr. Best Practice“ nach allen Maßstäben. Gibt es einen bestimmten Grund, warum es schlecht ist, einen „Repeater“ zu haben, der keine Liste ist? Mir fällt keiner ein.
Haftungsausschluss: Ich habe erst vor kurzem als Entwickler angefangen zu arbeiten, daher bin ich keineswegs ein Experte auf diesem Gebiet.
Ich glaube nicht, dass es ein Problem ist, Elemente zu wiederholen, ohne unbedingt eine Liste zu verwenden, aber in diesem Beispiel werden die Elemente basierend auf dem Layout und nicht auf ihrem Kontext gruppiert.
Was nicht so anders ist als das hier
Das gruppiert jeweils 2 Elemente zu einer Gruppe, was auf eine Beziehung zwischen den 2 Elementen hindeutet, was in diesem Fall nicht stimmt. Eine genauere Darstellung ist
Abgesehen davon, dass die Daten genauer dargestellt werden, ist es auch flexibler.
Nehmen wir an, wir möchten einen Filter implementieren, der Element 2 ausblendet.
Mit der `
` können Sie einfach die `- ` entfernen.
` müssen Sie die Liste leeren und dann ohne Element 2 neu befüllen, um keinen leeren Platz in Zeile 1 zu hinterlassen.
Mit den Spalten-`
Es ist nicht schlecht, aber eine Liste ist besser. Mit Sprache kann man zum Beispiel sagen: „Wähle das 5. aus der Liste“. Auch andere Websites können Ihre Liste verwenden, zum Beispiel: Google verwendet den Listenindex von Wikipedia in den blauen Links am Ende eines Suchergebnisses.
Ich denke, ich würde Python verwenden, um dieses Problem zu lösen.
Python ist immer die Antwort. Die Frage ist irrelevant.
Ich habe Ihr Beispiel geforkt und es scheint, dass es in Ordnung ist, mehrere Alben in jede Spalte zu setzen. https://codepen.io/dna113p/project/editor/ApjRav
OMG – das ist wirklich gut. Ungerade Zahlen kommen in die erste Spalte und gerade Zahlen in die zweite.
Aber was passiert im Szenario „unbekannte Unterseite“? Wenn Sie mit einer ungeraden Zahl enden, kommt diese in Spalte eins. Wenn der nächste Stapel von Elementen hereinkommt, beginnen Sie wieder mit eins, und dieses kommt ebenfalls in die erste Spalte.
Ich bin mir wegen der unbekannten Unterseite nicht wirklich sicher und kenne mich nicht gut mit RxJS aus. Es scheint mir, dass, solange Sie eine Möglichkeit haben, die neu gestreamten Elemente mit `setState` zu Ihrem Alben-Array hinzuzufügen, React die neuen Alben korrekt rendern wird.
Es gibt viele gute Antworten hier, die sich dem Problem von der CSS-Seite nähern, was wahrscheinlich der richtige Weg ist, aber um Ihre Frage zu beantworten, auch wenn es nicht die „richtige“ Frage ist…
Es gibt zwei Szenarien, in denen der Umgang mit Arrays in React schwierig wird…
Das erste ist, wenn Sie 0 oder mehr Einträge aus jedem Element des Arrays generieren möchten. In diesem Fall sollten Sie
Das zweite ist, wenn Sie Array-Elemente gruppieren möchten, entweder nach Anzahl oder nach etwas anderem. In diesem Fall haben Sie ein paar Optionen. Sie können immer noch den Array-Reduce-Trick verwenden und nur dann „pushen“, wenn Sie Ihre Schwelle erreichen (gut genug für einfache Fälle) oder Sie können Generatoren als Konverter verwenden (besser für komplexe Logik).
Reduce-Technik: https://codepen.io/jamon/pen/VywvXL
Generator-Technik: https://codepen.io/jamon/pen/QaWbPd
Diese Antwort war super faszinierend für mich – insbesondere die `reduce`-Funktion, da ich sie noch nie verwendet habe.
Gibt es im Fall von `reduce` eine Möglichkeit, den Akkumulator global zu machen, damit er, wenn ein neuer Block von Elementen über einen Stream hereinkommt, einfach hinzugefügt werden kann?
Außerdem, was ist der Vorteil von Reduce gegenüber Map? Ist es die Performance, da man nur den Akkumulator anstelle jedes einzelnen Elements zurückgibt?
Um den Teil „bodenlos“ zu klären, sollten Sie React das für Sie erledigen lassen – wenn Sie sich dem Ende nähern, fordern Sie mehr Ergebnisse an, rendern Sie mehr Ergebnisse. Wenn Sie den Speicher besser verwalten möchten, können Sie Ergebnisse aus dem DOM „freigeben“, die nicht auf dem Bildschirm sind, und die Positionen entsprechend anpassen (dies ist ebenfalls ein schwieriges Problem, aber ein schwieriges Problem, das weit verbreitet gelöst ist und viele Beispiele zu finden sind).
Die „unbekannte Unterseite“ ist ein roter Hering, darum sollten Sie sich nicht kümmern; Sobald Sie im „Infinite Scroll“-Bereich sind, müssen Sie zusätzlichen Zustand beibehalten (d.h. abgerufene Alben, Scrollposition) und basierend darauf rendern.
Um zu Ihrem ursprünglichen Rätsel zurückzukehren, war die DRY-Lösung, die ich gefunden habe, die Verwendung von `lodash/chunk` und zwei Maps
https://codepen.io/otsdr/project/editor/XLpxdO
„Nun nehmen wir an, das CSS-Framework, das Sie verwenden, erfordert, dass Sie rendern…“
Hahahhahahahaha…. nein.
Diese lächerlichen Anforderungen, aber!
Aber im Ernst – ich habe früher im Gesundheitswesen gearbeitet und wir mussten uns durch alberne Hürden kämpfen, wegen all der Einschränkungen, die wir nicht kontrollieren konnten. Dieses Beispiel ist konstruiert, aber der Kampf ist real.
Sollte es nicht einfach so funktionieren?
Spalte 1a
Spalte 2a
Spalte 1b
Spalte 2b
Spalte 1c
Spalte 2c
… ?
So viele Leute scheinen den ganzen Sinn des Rätsels zu verfehlen. Der Punkt ist, das CSS NICHT zu ändern. Das ist der „Rätsel“-Teil. Der Punkt ist, innerhalb der Grenzen des beschriebenen Layouts zu bleiben. Sie gehen doch auch nicht zu den Kreuzworträtseln der NY Times und ordnen die Blöcke einfach neu an, damit sie zu Ihren Antworten passen, oder?
Auf jeden Fall bin ich kein Fan von zusätzlichen Renderfunktionen innerhalb von Komponenten. Ich denke, das verkompliziert die Logik und es ist viel klarer, diese Renderfunktionen einfach in ihre eigenen Komponenten aufzuteilen.
Ich bin auch kein Fan von „Vorab-Berechnen“ des Zustands, da dies die Dinge zusätzlich verkompliziert. Wir können immer `componentShouldUpdate` verwenden, um unnötige Neuberechnungen zu verhindern.
Ich denke, eine vereinfachte Version des ersten Ansatzes mit einer einzigen Renderfunktion wäre das, womit ich mich entscheiden würde. So etwas wie
Einige Probleme, die ich bemerkt habe
Array-Indizes sollten nicht als Schlüssel verwendet werden.
Es scheint, dass die vorgeschlagenen Implementierungen eine gerade Anzahl von Elementen annehmen? Wenn es eine ungerade Anzahl von Alben gibt, wird das letzte Album nicht gerendert.
Ich stimme Ihnen bei den zusätzlichen Renderfunktionen zu. Rendern ist eine Abstraktion. Jedes Mal, wenn Sie eine weitere Renderfunktion hinzufügen, erstellen Sie eine weitere Abstraktion, und das macht es schwieriger, das ursprüngliche Rendern zu verstehen.
Interessante Gedanken zu „vorbereitetem“ Zustand. Ich mag es auch nicht, da es eine weitere Mutation ist, die man verstehen muss, um den gesamten Fluss zu verstehen.
Ich habe in diesem Kommentar viel gelernt. Danke!
Ich habe den wichtigen Code aus meinem Beispiel nicht inline eingefügt, was wahrscheinlich für Leute, die das auf Mobilgeräten oder anderweitig lesen, hilfreich wäre
dies ermöglicht
Das hat nichts mit dem Coden selbst zu tun, aber ich schätze Ihre Musikauswahl :)
#nieVergessen
React 16 erlaubt es, festzulegen, wo im DOM eine Komponente gerendert werden soll.
Jede ungerade Spalte rendert normalerweise . .columns > .column.
Jede gerade rendert .column in die letzte .columns
Hmm.
Da der gesamte Punkt ist, dass das CSS nicht geändert werden kann
Was mir auch erlaubt, später zu sagen, ob es vom zugrundeliegenden CSS unterstützt wird, es von 2-up auf 3-up oder andere solche Dinge zu ändern (Änderung von @columns auf dem Grid-Tag), oder das CSS ändert sich so, dass die Konstruktion des DOM auf eine bestimmte Weise, um diese Spalten zu erhalten, nicht notwendig ist, zum Beispiel
Dann
Eines, das mir an dieser Lösung sehr gefällt, ist, dass das Grid jetzt eine separate Komponente ist, was die Rendering-Logik vereinfachen wird. Sie müssen nur davon ausgehen, dass es ein Grid gibt. Wenn Sie wissen möchten, *wie* das Grid funktioniert, schauen Sie sich die Grid-Komponente an.
Kann man nicht einfach Flexbox verwenden oder sich anderweitig auf CSS verlassen? Der Versuch, es mit Divs zu lösen, vermischt die Zuständigkeiten und ist die Ursache Ihrer Probleme.
Sie haben definitiv Recht. Der Punkt, den ich hier machen wollte, ist, dass wir manchmal keine Kontrolle über die Umgebung haben und kreativ werden müssen. In meiner beruflichen Erfahrung wurde mir öfter eine riesige Unordnung gegeben und gesagt „Mach, dass das funktioniert“, als eine leere Leinwand.
Ich bin zwar an der Diskussion interessiert, wollte aber hauptsächlich Ihren guten Musikgeschmack erwähnen.
;)