Wie kann ich sich wiederholende React-Elemente rendern?

81

Ich habe Code geschrieben, um sich wiederholende Elemente in ReactJS zu rendern, aber ich hasse es, wie hässlich es ist.

render: function(){
  var titles = this.props.titles.map(function(title) {
    return <th>{title}</th>;
  });
  var rows = this.props.rows.map(function(row) {
    var cells = [];
    for (var i in row) {
      cells.push(<td>{row[i]}</td>);
    }
    return <tr>{cells}</tr>;
  });
  return (
    <table className="MyClassName">
      <thead>
        <tr>{titles}</tr>
      </thead>
      <tbody>{rows}</tbody>
    </table>
  );
} 

Gibt es einen besseren Weg, um dies zu erreichen?

(Ich möchte forSchleifen in den Vorlagencode oder einen ähnlichen Ansatz einbetten .)

Fadedbee
quelle
3
Ja, das ist was ich will, aber die akzeptierte Antwort entspricht weitgehend dem hässlichen Code, den ich bereits schreibe. Es muss einen besseren Weg geben ...
Fadedbee
Ich hoffe, dies wird dazu beitragen, stackoverflow.com/a/37600679/1785635
abhirathore2006

Antworten:

130

Sie können Ausdrücke in geschweifte Klammern setzen. Beachten Sie im kompilierten JavaScript, warum eine forSchleife innerhalb der JSX-Syntax niemals möglich wäre. JSX entspricht Funktionsaufrufen und gezuckerten Funktionsargumenten. Es sind nur Ausdrücke zulässig.

(Außerdem: Denken Sie daran key, Komponenten, die in Schleifen gerendert werden , Attribute hinzuzufügen .)

JSX + ES2015 :

render() {
  return (
    <table className="MyClassName">
      <thead>
        <tr>
          {this.props.titles.map(title =>
            <th key={title}>{title}</th>
          )}
        </tr>
      </thead>
      <tbody>
        {this.props.rows.map((row, i) =>
          <tr key={i}>
            {row.map((col, j) =>
              <td key={j}>{col}</td>
            )}
          </tr>
        )}
      </tbody>
    </table>
  );
} 

JavaScript :

render: function() {
  return (
    React.DOM.table({className: "MyClassName"}, 
      React.DOM.thead(null, 
        React.DOM.tr(null, 
          this.props.titles.map(function(title) {
            return React.DOM.th({key: title}, title);
          })
        )
      ), 
      React.DOM.tbody(null, 
        this.props.rows.map(function(row, i) {
          return (
            React.DOM.tr({key: i}, 
              row.map(function(col, j) {
                return React.DOM.td({key: j}, col);
              })
            )
          );
        })
      )
    )
  );
} 
Ross Allen
quelle
Schöne Fänge. Ich fügte die fehlende Klammer oben hinzu und wechselte von forzu a map. Da ich nicht weiß, was die Daten in jeder der Datenstrukturen sind, habe ich angenommen, dass die Zeilen Arrays sind, und den Index der Iteration für die verwendet key. Wenn Daten zum Array hinzugefügt / daraus entfernt werden, führt dies zu unnötigem erneuten Rendern. Sie sollten keyzu einem Wert wechseln , der die Daten eindeutig identifiziert und nicht von der Reihenfolge und / oder Größe des Arrays abhängt.
Ross Allen
@ssorallen Danke für deine Antwort. Ich werde diese Frage für ein paar Tage offen lassen, falls es eine bessere Möglichkeit gibt, dies zu tun, bevor ich Ihre Antwort akzeptiere. Ich denke, mein Problem ist, dass JSX weder ein Escape ohne Ausdruck für forSchleifen und ähnliches zu haben scheint , noch eine Vorlagensyntax für Schleifen hat.
Fadedbee
1
@chrisdew JSX ist keine Vorlagensprache, sondern syntaktischer Zucker für einfaches altes JavaScript. Sie erhalten keine Operatoren, wie Sie sie von Vorlagensprachen erwarten. Versuchen Sie, den Code in dieser Antwort in den Live- JSX-Compiler einzufügen, um zu verstehen, was er tut und warum eine forSchleife niemals möglich wäre.
Ross Allen
Könnten Sie für jede Zeile ein onClick-Ereignis hinzufügen und das Zeilenelement an die Funktion übergeben? Ich versuche so etwas, aber ich erhalte Uncaught TypeError: Die Eigenschaft 'clickHandler' von undefined clickHandler () {console.log ('on click row') kann nicht gelesen werden. } <tbody> {this.props.rows.map (Funktion (Zeile, i) {return (<tr key = {i} onClick = this.clickHandler ()> {row.map (Funktion (col, j) {return <td key = {j}> {col} </ td>;})} </ tr>);})} </ tbody>
Kiran
Egal, ich habe der Kartenmethode .bind (this) hinzugefügt, dann wird die Methode für jede Zeile einbezogen. Vielen Dank.
Kiran
13

Um die Antwort von Ross Allen zu erweitern, finden Sie hier eine etwas sauberere Variante mit ES6-Pfeilsyntax.

{this.props.titles.map(title =>
  <th key={title}>{title}</th>
)}

Es hat den Vorteil, dass der JSX-Teil isoliert ist (nein returnoder ;), was es einfacher macht, eine Schleife um ihn herum zu legen.

Jodiug
quelle
13

Da Array(3)ein nicht iterierbares Array erstellt wird , muss es ausgefüllt werden, um die Verwendung der mapArray-Methode zu ermöglichen. Eine Möglichkeit zum "Konvertieren" besteht darin, es in Array-Klammern zu zerstören, wodurch das Array "gezwungen" wird, mit undefinedWerten gefüllt zu werdenArray(N).fill(undefined)

<table>
    { [...Array(3)].map((_, index) => <tr key={index}/>) }
</table>

Ein anderer Weg wäre über Array fill():

<table>
    { Array(3).fill(<tr/>) }
</table>

Das Problem mit dem obigen Beispiel ist das Fehlen von keyRequisiten, was ein Muss ist .
(Verwenden eines Iterators indexwie keynicht empfohlen)


Verschachtelte Knoten:

const tableSize = [3,4]
const Table = (
    <table>
        <tbody>
        { [...Array(tableSize[0])].map((tr, trIdx) => 
            <tr key={trIdx}> 
              { [...Array(tableSize[1])].map((a, tdIdx, arr) => 
                  <td key={trIdx + tdIdx}>
                  {arr.length * trIdx + tdIdx + 1}
                  </td>
               )}
            </tr>
        )}
        </tbody>
    </table>
);

ReactDOM.render(Table, document.querySelector('main'))
td{ border:1px solid silver; padding:1em; }
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<main></main>

vsync
quelle
2
Ich habe mindestens 2 Stunden damit verbracht, in der Renderfunktion eine Schleife innerhalb der Schleife zu erstellen. Vielen Dank. du hast meinen Tag gerettet.
Shwe
2
Könnten Sie dies nicht tun, anstatt eine Karte zu verwenden? Array.from({length: 5}, (value, index) => <tr />)
creativehandle
2

Lassen Sie uns im Sinne der funktionalen Programmierung die Arbeit mit unseren Komponenten mithilfe von Abstraktionen etwas vereinfachen.

// converts components into mappable functions
var mappable = function(component){
  return function(x, i){
    return component({key: i}, x);
  }
}

// maps on 2-dimensional arrays
var map2d = function(m1, m2, xss){
  return xss.map(function(xs, i, arr){
    return m1(xs.map(m2), i, arr);
  });
}

var td = mappable(React.DOM.td);
var tr = mappable(React.DOM.tr);
var th = mappable(React.DOM.th);

Jetzt können wir unseren Render folgendermaßen definieren:

render: function(){
  return (
    <table>
      <thead>{this.props.titles.map(th)}</thead>
      <tbody>{map2d(tr, td, this.props.rows)}</tbody>
    </table>
  );
}

jsbin


Eine Alternative zu unserer map2d wäre eine Curry-Map-Funktion, aber die Leute scheuen das Currying.

Räuber
quelle
Diese Funktionen geben dem Entwickler nicht die Möglichkeit, das keyAttribut anzugeben . Die Verwendung des Index bedeutet, dass durch Hinzufügen / Entfernen von Elementen das erneute Rendern von Objekten erzwungen wird, die sich möglicherweise nicht geändert haben.
Ross Allen
Richtig, aber in 95% der Fälle sind die indexbasierten Schlüssel ausreichend. Bei den etwas seltenen Ausnahmen würde ich lieber ein wenig aus dem Weg gehen.
Brigand
Einverstanden, es wäre einfach genug sicherzustellen, dass die Schlüssel auf jeder Ebene eindeutig sind, wenn es nötig wäre, ich mag es.
Ryan Ore
2

Dies ist imo der eleganteste Weg, dies zu tun (mit ES6). Instanziieren Sie Ihr leeres Array mit 7 Indizes und ordnen Sie es in einer Zeile zu:

Array.apply(null, Array(7)).map((i)=>
<Somecomponent/>
)

Ein großes Lob an https://php.quicoto.com/create-loop-inside-react-jsx/

Chris
quelle
1
Nett! Scheint, Sie können auch tunArray(...Array(10)).map((v, i) => <SomeComponent />)
Magritte