Як я можу зробити повторювані елементи React?


81

Я написав якийсь код для відтворення повторюваних елементів у ReactJS, але ненавиджу, наскільки це потворно.

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

Чи є кращий спосіб цього досягти?

(Я хотів би вбудувати forцикли в код коду шаблону або подібний підхід.)



3
Так, це те, що я хочу, але прийнята відповідь майже однакова з потворним кодом, який я вже пишу. Має бути кращий спосіб ...
fadedbee

я сподіваюся , що це допоможе stackoverflow.com/a/37600679/1785635
abhirathore2006

Відповіді:


130

Ви можете помістити вирази всередину фігурних дужок. Зверніть увагу у складеному JavaScript, чому forцикл ніколи не буде можливим у синтаксисі JSX; JSX дорівнює викликам функцій та аргументам цукрової функції. Допускаються лише вирази.

(Також: Не забудьте додати keyатрибути до компонентів, що відображаються всередині циклів.)

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

Хороший улов. Я додав відсутню дужку вгорі і перейшов з forна map. Оскільки я не знаю, які дані є в кожній зі структур даних, я припустив, що рядки є масивами, і використовував індекс ітерації для key. Коли дані додаються / видаляються з масиву, це призведе до непотрібного повторного відтворення. Вам слід перейти keyдо значення, яке однозначно ідентифікує дані і яке не залежить від порядку та / або розміру масиву.
Росс Аллен,

@ssorallen Дякую за відповідь. Це питання залишатимуться відкритими на кілька днів, якщо є більш приємний спосіб зробити це, перш ніж прийняти вашу відповідь. Я думаю, що моя проблема полягає в тому, що JSX, схоже, не має виразу без виразу, для forциклів та подібного, а також не має синтаксису шаблону для циклів.
fadedbee

1
@chrisdew JSX не є мовою шаблону, це синтаксичний цукор для простого старого JavaScript. Ви не отримаєте операторів, як ви очікуєте від мов шаблонів. Спробуйте вставити код у цій відповіді в живий компілятор JSX, щоб зрозуміти, що він робить, і чому forцикл ніколи не буде можливим.
Росс Аллен,

Чи можете ви додати подію onClick для кожного рядка та передати елемент рядка функції? Я намагаюся щось подібне, але я отримую Uncaught TypeError: Не вдається прочитати властивість 'clickHandler' невизначеного clickHandler () {console.log ('на рядку кліку'); } <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>
Кіран,

Неважливо, я додав .bind (this) до методу map, тоді метод бере участь у кожному рядку. Дякую.
Кіран

14

Оскільки Array(3)буде створений неітерабельний масив , його потрібно заповнити, щоб дозволити використання mapметоду Array. Спосіб "перетворення" полягає в тому, щоб знищити його всередині масивів-дужок, що "змушує" масив заповнюватися undefinedзначеннями, такими ж, якArray(N).fill(undefined)

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

Інший спосіб - через Array fill():

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

⚠️ Проблема з наведеним вище прикладом полягає у відсутності keyреквізиту, що є необхідним .
(Використовувати ітератор, indexяк keyне рекомендується)


Вкладені вузли:

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>


2
Я витратив щонайменше 2 години, щоб мати можливість петлі всередині циклу у функції рендерингу. Дякую. ти врятував мій день.
Шве,

2
Не могли б ви зробити це замість використання карти? Array.from({length: 5}, (value, index) => <tr />)
creativehandle

13

Щоб розширити відповідь Росса Аллена, ось трохи чистіший варіант із використанням синтаксису стрілок ES6.

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

Ця перевага полягає в тому, що частина JSX є ізольованою (ні returnабо ;), що полегшує розміщення петлі навколо неї.


2

У дусі функціонального програмування давайте полегшимо роботу з нашими компонентами, використовуючи абстракції.

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

Тепер ми можемо визначити наш візуалізатор так:

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

jsbin


Альтернативою нашій map2d була б функція карризованої карти, але люди, як правило, уникають каррінгу.


Ці функції не дають розробнику можливості вказати keyатрибут. Використання індексу означає, що додавання / видалення елементів змусить повторно відтворювати об'єкти, які могли не змінитися.
Росс Аллен

Правда, але 95% випадків достатньо ключів на основі індексу. Я волів би трохи відійти від дещо рідкісних винятків.
Brigand

Погоджено, було б досить легко переконатися, що ключі є унікальними на будь-якому даному рівні, якщо б це було потрібно, мені це подобається.
Райан Оре

2

Це, imo, найелегантніший спосіб це зробити (з ES6). Примірник порожнього масиву з 7 індексами та картою в один рядок:

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

похвала на https://php.quicoto.com/create-loop-inside-react-jsx/


1
Приємно! Здається, ви теж можете це зробитиArray(...Array(10)).map((v, i) => <SomeComponent />)
магріт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.