Jak mogę renderować powtarzające się elementy React?

81

Napisałem kod do renderowania powtarzających się elementów w ReactJS, ale nienawidzę tego, jak brzydki jest.

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>
  );
} 

Czy jest lepszy sposób na osiągnięcie tego?

(Chciałbym osadzić forpętle w kodzie szablonu lub w podobnym podejściu).

fadedbee
źródło
3
Tak, tego chcę, ale zaakceptowana odpowiedź jest taka sama, jak brzydki kod, który już piszę. Musi być lepszy sposób ...
fadedbee
mam nadzieję, że to pomoże stackoverflow.com/a/37600679/1785635
abhirathore2006

Odpowiedzi:

130

Możesz umieścić wyrażenia w nawiasach klamrowych. Zwróć uwagę w skompilowanym JavaScript, dlaczego forpętla nigdy nie byłaby możliwa w składni JSX; JSX to wywołania funkcji i sugerowane argumenty funkcji. Dozwolone są tylko wyrażenia.

(Ponadto: pamiętaj, aby dodać keyatrybuty do komponentów renderowanych wewnątrz pętli).

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
źródło
Niezłe połowy. Dodałem brakujący nawias na górze i przełączane z forDo map. Ponieważ nie wiem, jakie dane znajdują się w każdej ze struktur danych, założyłem, że wiersze są tablicami i użyłem indeksu iteracji dla key. Gdy dane są dodawane / usuwane z tablicy, spowoduje to niepotrzebne ponowne renderowanie. Należy przełączyć keysię na wartość, która jednoznacznie identyfikuje dane i która nie jest zależna od kolejności i / lub rozmiaru tablicy.
Ross Allen,
@ssorallen Dzięki za odpowiedź. Zostawię to pytanie otwarte na kilka dni, na wypadek gdyby był lepszy sposób na zrobienie tego, zanim zaakceptuję twoją odpowiedź. Myślę, że moim problemem jest to, że JSX nie wydaje się mieć ucieczki bez wyrażeń, dla forpętli i podobnych, ani nie ma składni szablonu dla pętli.
fadedbee
1
@chrisdew JSX nie jest językiem szablonów, to cukier składniowy dla zwykłego starego JavaScript. Nie dostaniesz operatorów, jakich oczekujesz od języków szablonów. Spróbuj wkleić kod z tej odpowiedzi do kompilatora JSX na żywo, aby zrozumieć, co robi i dlaczego forpętla nigdy nie byłaby możliwa.
Ross Allen
Czy możesz dodać zdarzenie onClick dla każdego wiersza i przekazać element wiersza do funkcji? Próbuję czegoś takiego, ale otrzymuję Uncaught TypeError: Nie można odczytać właściwości „clickHandler” niezdefiniowanej funkcji clickHandler () {console.log („w wierszu kliknięcia”); } <tbody> {this.props.rows.map (function (row, i) {return (<tr key = {i} onClick = this.clickHandler ()> {row.map (function (col, j) {return <td key = {j}> {col} </td>;})} </tr>);})} </tbody>
Kiran,
Nieważne, dodałem .bind (this) do metody mapy, wtedy metoda jest zaangażowana dla każdego wiersza. Dziękuję Ci.
Kiran
13

Aby rozwinąć odpowiedź Rossa Allena, oto nieco czystszy wariant wykorzystujący składnię strzałek ES6.

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

Ma tę zaletę, że część JSX jest izolowana (nie returnlub ;), co ułatwia umieszczenie wokół niej pętli.

Jodiug
źródło
13

Ponieważ Array(3)utworzy tablicę , której nie można iterować , należy ją wypełnić, aby umożliwić użycie mapmetody Array. Sposobem na „konwersję” jest zniszczenie go w nawiasach Array, co „wymusza” wypełnienie tablicy undefinedwartościami, tak samo jakArray(N).fill(undefined)

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

Innym sposobem byłoby użycie Array fill():

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

⚠️ Problemem z powyższym przykładem jest brak keypodpory, co jest koniecznością .
(Używanie iteratorów, indexponieważ keynie jest zalecane)


Węzły zagnieżdżone:

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
źródło
2
Spędziłem co najmniej 2 godziny, aby móc zapętlić się wewnątrz pętli w funkcji renderowania. dzięki. uratowałeś mi dzień.
Shwe
2
Nie mógłbyś tego zrobić zamiast używać mapy? Array.from({length: 5}, (value, index) => <tr />)
creativehandle
2

W duchu programowania funkcjonalnego sprawmy, aby nasze komponenty były nieco łatwiejsze w obsłudze, używając abstrakcji.

// 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);

Teraz możemy zdefiniować nasz render w ten sposób:

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

jsbin


Alternatywą dla naszego map2d byłaby funkcja mapy curried, ale ludzie unikają curry.

Bandyta
źródło
Te funkcje nie dają programistom możliwości określenia keyatrybutu. Korzystanie z indeksu oznacza, że ​​dodanie / usunięcie elementów wymusi ponowne renderowanie obiektów, które mogły się nie zmienić.
Ross Allen,
To prawda, ale w 95% przypadków klucze oparte na indeksach są wystarczające. Wolałbym trochę zejść z drogi w nieco rzadkich wyjątkach.
Brigand
Zgoda. Byłoby łatwo zapewnić, że klucze są niepowtarzalne na każdym poziomie, gdyby było to konieczne. Podoba mi się.
Ryan Ore
2

To jest, imo, najbardziej elegancki sposób na zrobienie tego (z ES6). Utwórz instancję pustej tablicy z 7 indeksami i mapuj w jednym wierszu:

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

Kudos to https://php.quicoto.com/create-loop-inside-react-jsx/

Chris
źródło
1
Miły! Wydaje się, że możesz to zrobićArray(...Array(10)).map((v, i) => <SomeComponent />)
magritte