Петля внутри React JSX

Я пытаюсь сделать что-то вроде следующего в React JSX (где ObjectRow является отдельным компонентом):

<tbody>
    for (var i=0; i < numrows; i++) {
        <ObjectRow/>
    } 
</tbody>

Я понимаю и понимаю, почему это недопустимо JSX, поскольку JSX отображается на вызовы функций. Однако, исходя из шаблонной земли и будучи новичком в JSX, я не уверен, как бы я достиг вышеизложенного (добавив компонент несколько раз).

Ответ 1

Думайте об этом, как будто вы просто вызываете функции JavaScript. Вы не можете использовать цикл for куда будут идти аргументы вызова функции:

return tbody(
    for (var i = 0; i < numrows; i++) {
        ObjectRow()
    } 
)

Посмотрите, как функции tbody передается цикл for в качестве аргумента, и, конечно, синтаксическая ошибка.

Но вы можете создать массив и передать его в качестве аргумента:

var rows = [];
for (var i = 0; i < numrows; i++) {
    rows.push(ObjectRow());
}
return tbody(rows);

Вы можете использовать в основном ту же структуру при работе с JSX:

var rows = [];
for (var i = 0; i < numrows; i++) {
    // note: we add a key prop here to allow react to uniquely identify each
    // element in this array. see: https://reactjs.org/docs/lists-and-keys.html
    rows.push(<ObjectRow key={i} />);
}
return <tbody>{rows}</tbody>;

Между прочим, мой пример JavaScript почти точно то, во что превращается этот пример JSX. Поиграйте с Babel REPL, чтобы понять, как работает JSX.

Ответ 2

Не уверен, что это будет работать для вашей ситуации, но часто карта - хороший ответ.

Если это был ваш код с циклом for:

<tbody>
    for (var i=0; i < objects.length; i++) {
        <ObjectRow obj={objects[i]} key={i}>
    } 
</tbody>

Вы могли бы написать это с картой:

<tbody>
    {objects.map(function(object, i){
        return <ObjectRow obj={object} key={i} />;
    })}
</tbody>

Синтаксис ES6:

<tbody>
    {objects.map((object, i) => <ObjectRow obj={object} key={i} />)}
</tbody>

Ответ 3

Если у вас еще нет массива для map(), как @FakeRainBrigand, ответьте и хотите вставить это, чтобы исходный макет соответствовал выходу ближе, чем ответ @SophieAlpert:

С синтаксисом ES2015 (ES6) (функции распространения и стрелки)

http://plnkr.co/edit/mfqFWODVy8dKQQOkIEGV?p=preview

<tbody>
  {[...Array(10)].map((x, i) =>
    <ObjectRow key={i} />
  )}
</tbody>

Re: переписывая с Babel, его страница оговорки говорит, что требуется для распространения, но в настоящее время (v5.8.23), что похоже, не имеет места при распространении фактического Array. У меня есть проблема с документацией, чтобы уточнить это. Но используйте на свой страх и риск или polyfill.

Vanilla ES5

Array.apply

<tbody>
  {Array.apply(0, Array(10)).map(function (x, i) {
    return <ObjectRow key={i} />;
  })}
</tbody>

Inline IIFE

http://plnkr.co/edit/4kQjdTzd4w69g8Suu2hT?p=preview

<tbody>
  {(function (rows, i, len) {
    while (++i <= len) {
      rows.push(<ObjectRow key={i} />)
    }
    return rows;
  })([], 0, 10)}
</tbody>

Комбинация методов из других ответов

Сохраняйте исходный макет, соответствующий выходу, но сделайте внутреннюю часть более компактной:

render: function () {
  var rows = [], i = 0, len = 10;
  while (++i <= len) rows.push(i);

  return (
    <tbody>
      {rows.map(function (i) {
        return <ObjectRow key={i} index={i} />;
      })}
    </tbody>
  );
}

С синтаксисом ES2015 и Array

С помощью Array.prototype.fill вы можете сделать это как альтернативу использованию спреда, как показано выше:

<tbody>
  {Array(10).fill(1).map((el, i) =>
    <ObjectRow key={i} />
  )}
</tbody>

(Я думаю, что вы действительно могли бы опустить какой-либо аргумент fill(), но я не на это 100%.) Благодаря @FakeRainBrigand для исправления моей ошибки в более ранней версии решения fill() (см. ревизии).

key

Во всех случаях key attr устраняет предупреждение с помощью сборки разработки, но недоступно в дочернем элементе. Вы можете передать дополнительный attr, если хотите, чтобы индекс был доступен в дочернем элементе. См. Списки и ключи для обсуждения.

Ответ 4

Просто используя метод map Array с синтаксисом ES6:

<tbody>
  {items.map(item => <ObjectRow key={item.id} name={item.name} />)} 
</tbody>

Не забывайте свойство key.

Ответ 5

Использование функции отображения массива - очень распространенный способ циклически проходить по массиву элементов и создавать компоненты в соответствии с ними в React. Это отличный способ создания цикла, который является довольно эффективным и аккуратным способом выполнения ваших циклов в JSX. Это не единственный способ сделать это, но предпочтительный способ.

Кроме того, не забудьте иметь уникальный ключ для каждой итерации, как требуется. Функция карты создает уникальный индекс из 0, но не рекомендуется использовать созданный индекс, но если ваше значение уникально или если есть уникальный ключ, вы можете использовать их:

<tbody>
  {numrows.map(x=> <ObjectRow key={x.id} />)}
</tbody>

Кроме того, несколько строк из MDN, если вы не знакомы с функцией map в Array:

map вызывает предоставленную функцию обратного вызова один раз для каждого элемента в массиве по порядку и создает новый массив из результатов. обратный вызов вызывается только для индексов массива, которым присвоены значения, в том числе неопределенные. Он не вызывается для отсутствующих элементов массива (то есть индексов, которые никогда не были установлены, которые были удалены или которым никогда не было присвоено значение).

обратный вызов вызывается с тремя аргументами: значением элемента, индексом элемента и объектом Array, по которому осуществляется обход.

Если для отображения предоставлен параметр thisArg, он будет использоваться в качестве обратного вызова для этого значения. В противном случае значение undefined будет использоваться как его значение this. Это значение, в конечном счете наблюдаемое обратным вызовом, определяется в соответствии с обычными правилами определения этого, видимого функцией.

map не изменяет массив, в котором он вызывается (хотя обратный вызов, если вызван, может сделать это).

Ответ 6

Если вы уже используете lodash, функция _.times удобна.

import React, { Component } from 'react';
import Select from './Select';
import _ from 'lodash';

export default class App extends Component {
    render() {
        return (
            <div className="container">
                <ol>
                    {_.times(3, i =>
                        <li key={i}>
                            <Select onSelect={this.onSelect}>
                                <option value="1">bacon</option>
                                <option value="2">cheez</option>
                            </Select>
                        </li>
                    )}
                </ol>
            </div>
        );
    }
}

Ответ 7

Вы также можете извлечь внешний блок возврата:

render: function() {
    var rows = [];
    for (var i = 0; i < numrows; i++) {
        rows.push(<ObjectRow key={i}/>);
    } 

    return (<tbody>{rows}</tbody>);
}

Ответ 8

Я знаю, что это старый поток, но вы можете проверить React Templates, который позволяет вам использовать шаблоны в стиле jsx в реакции с несколькими директивами (такими как rt-repeat).

Ваш пример, если бы вы использовали ответные шаблоны, был бы:

<tbody>
     <ObjectRow rt-repeat="obj in objects"/>
</tbody>

Ответ 9

если numrows - массив, и это очень просто.

<tbody>
   {numrows.map(item => <ObjectRow />)}
</tbody>

Тип данных Array в React очень хорош, массив может возвращать новый массив и поддерживать фильтр, уменьшать и т.д.

Ответ 10

Есть несколько способов сделать это. JSX в конечном итоге компилируется в JavaScript, поэтому, пока вы пишете правильный JavaScript, вы будете в порядке.

Мой ответ нацелен на объединение всех чудесных способов, уже представленных здесь:

Если у вас нет массива объектов, просто количество строк:

в return блоке создаем Array и используем Array.prototype.map:

render() {
  return (
    <tbody>
      {Array(numrows).fill(null).map((value, index) => (
        <ObjectRow key={index}>
      ))}
    </tbody>
  );
}

за пределами блока return просто используйте обычный цикл for для JavaScript:

render() {
  let rows = [];
  for (let i = 0; i < numrows; i++) {
    rows.push(<ObjectRow key={i}/>);
  } 
  return (
    <tbody>{rows}</tbody>
  );
}

Сразу же вызывается выражение функции:

render() {
  return (
    <tbody>
      {() => {
        let rows = [];
        for (let i = 0; i < numrows; i++) {
          rows.push(<ObjectRow key={i}/>);
        }
        return rows;
      }}
    </tbody>
  );
}

Если у вас есть массив объектов

внутри return блока .map() каждый объект для компонента <ObjectRow>:

render() {
  return (
    <tbody>
      {objectRows.map((row, index) => (
        <ObjectRow key={index} data={row} />
      ))}
    </tbody>
  );
}

за пределами блока return просто используйте обычный цикл for для JavaScript:

render() {
  let rows = [];
  for (let i = 0; i < objectRows.length; i++) {
    rows.push(<ObjectRow key={i} data={objectRows[i]} />);
  } 
  return (
    <tbody>{rows}</tbody>
  );
}

Сразу же вызывается выражение функции:

render() {
  return (
    <tbody>
      {(() => {
        const rows = [];
        for (let i = 0; i < objectRows.length; i++) {
          rows.push(<ObjectRow key={i} data={objectRows[i]} />);
        }
        return rows;
      })()}
    </tbody>
  );
}

Ответ 11

Существует несколько ответов, указывающих на использование оператора map. Вот полный пример использования итератора в компоненте FeatureList в список компонентов Feature, основанные на структуре JSON данных под названием функции.

const FeatureList = ({ features, onClickFeature, onClickLikes }) => (
  <div className="feature-list">
    {features.map(feature =>
      <Feature
        key={feature.id}
        {...feature}
        onClickFeature={() => onClickFeature(feature.id)}
        onClickLikes={() => onClickLikes(feature.id)}
      />
    )}
  </div>
); 

Вы можете просмотреть полный список FeatureList на GitHub. Здесь перечислены функции.

Ответ 12

скажем, у нас есть массив элементов в вашем состоянии:

[{name: "item1", id: 1}, {name: "item2", id: 2}, {name: "item3", id: 3}]

<tbody>
    {this.state.items.map((item) => {
        <ObjectRow key={item.id} name={item.name} />
    })} 
</tbody>

Ответ 13

... Или вы также можете подготовить массив объектов и сопоставить его с функцией для получения желаемого результата. Я предпочитаю это, потому что это помогает мне поддерживать хорошую практику кодирования без логики внутри возврата рендера.

render() {
const mapItem = [];
for(let i =0;i<item.length;i++) 
  mapItem.push(i);
const singleItem => (item, index) {
 // item the single item in the array 
 // the index of the item in the array
 // can implement any logic here
 return (
  <ObjectRow/>
)

}
  return(
   <tbody>{mapItem.map(singleItem)}</tbody>
  )
}

Ответ 14

Просто используйте .map() чтобы <ObjectRow> вашу коллекцию и вернуть <ObjectRow> с реквизитами с каждой итерации.

Предполагаемые objects - это массив где-нибудь...

<tbody>
  { objects.map((obj, index) => <ObjectRow obj={ obj } key={ index }/> ) }
</tbody>

Ответ 15

Если вы решите преобразовать это внутри метода return() метода render, проще всего будет использовать метод map(). Сопоставьте ваш массив с синтаксисом JSX с помощью функции map(), как показано ниже (используется синтаксис ES6).


Внутри родительского компонента:

<tbody>
   { objectArray.map((object, index) => <ObjectRow key={index} object={object}>) }
</tbody>

Обратите внимание на атрибут key, добавленный к вашему дочернему компоненту. Если вы не указали ключевой атрибут, вы можете увидеть следующее предупреждение на своей консоли.

Предупреждение: каждый дочерний элемент в массиве или итераторе должен иметь уникальная "ключевая" опора.


Теперь в компоненте ObjectRow вы можете получить доступ к объекту из его свойств.

Внутри компонента ObjectRow

const { object } = this.props

или

const object = this.props.object

Это должно получить вам объект, который вы передали из родительского компонента в переменную object в компоненте ObjectRow. Теперь вы можете выплевывать значения в этом объекте в соответствии с вашей целью.


Рекомендации:

метод map() в Javascript

ECMA Script 6 или ES6

Ответ 16

Функция ES2015/Babel использует функцию генератора для создания массива JSX:

function* jsxLoop(times, callback)
{
    for(var i = 0; i < times; ++i)
        yield callback(i);
}

...

<tbody>
    {[...jsxLoop(numrows, i =>
        <ObjectRow key={i}/>
    )]}
</tbody>

Ответ 17

Я использую это:

gridItems = this.state.applications.map(app =>
          <ApplicationItem key={app.Id} app={app } />
);

PD: никогда не забывайте ключ, иначе у вас будет много предупреждений!

Ответ 18

Я предпочитаю подход, когда логика программирования происходит за пределами возвращаемого значения render. Это помогает сохранить то, что на самом деле легко получить.

Поэтому я бы, наверное, сделал что-то вроде:

import _ from 'lodash';

...

const TableBody = ({ objects }) => {
  const objectRows = objects.map(obj => <ObjectRow object={obj} />);      

  return <tbody>{objectRows}</tbody>;
} 

По общему признанию, это такая небольшая сумма кода, в которой она может работать нормально.

Ответ 19

Чтобы выполнить цикл несколько раз и вернуться, вы можете выполнить его с помощью from и map:

<tbody>
  {
    Array.from(Array(i)).map(() => <ObjectRow />)
  }
</tbody>

где i = number of times

Ответ 20

Ваш код JSX будет скомпилирован в чистый код JavaScript, любые теги будут заменены объектами ReactElement. В JavaScript вы не можете вызывать функцию несколько раз для сбора возвращаемых переменных.

Это незаконно, единственный способ - использовать массив для хранения возвращаемых функций.

Или вы можете использовать Array.prototype.map который доступен с JavaScript ES5 для обработки этой ситуации.

Может быть, мы сможем написать другой компилятор, чтобы воссоздать новый синтаксис JSX для реализации функции повторения, подобно Angular ng-repeat.

Ответ 21

вы можете, конечно, решить с помощью .map, как было предложено другим ответом. Если вы уже используете babel, вы можете подумать об использовании jsx-control-statements Они требуют немного настройки, но я думаю, что это стоит с точки зрения удобочитаемости (особенно для нереактивного разработчика). Если вы используете linter, также eslint-plugin-jsx-control-statements

Ответ 22

ES2015 Array.from с функцией карты + клавиша

Если у вас нет ничего для .map(), вы можете использовать Array.from() с функцией map для повторения элементов:

<tbody>
  {Array.from({ length: 5 }, (value, key) => <ObjectRow key={key} />)}
</tbody>

Ответ 23

Это можно сделать несколькими способами.

  • Как было предложено выше, перед return сохраните все элементы в массиве
  • Цикл внутри return

    Способ 1

     let container =[];
        let arr = [1,2,3] //can be anything array, object 
    
        arr.forEach((val,index)=>{
          container.push(<div key={index}>
                         val
                         </div>)
            /** 
            * 1. All loop generated elements require a key 
            * 2. only one parent element can be placed in Array
            * e.g. container.push(<div key={index}>
                                        val
                                  </div>
                                  <div>
                                  this will throw error
                                  </div>  
                                )
            **/   
        });
        return (
          <div>
             <div>any things goes here</div>
             <div>{container}</div>
          </div>
        )
    

    Способ 2

       return(
         <div>
         <div>any things goes here</div>
         <div>
            {(()=>{
              let container =[];
              let arr = [1,2,3] //can be anything array, object 
              arr.forEach((val,index)=>{
                container.push(<div key={index}>
                               val
                               </div>)
                             });
                        return container;     
            })()}
    
         </div>
      </div>
    )
    

Ответ 24

Вот простое решение.

var Object_rows=[];
for (var i=0; i < numrows; i++) {
    Object_rows.push(<ObjectRow/>)
} 
<tbody>
  {Object_rows}
</tbody>

Нет необходимости в отображении и сложном коде. Вам просто нужно поместить строки в массив и вернуть значения для его визуализации.

Ответ 25

Поскольку вы пишете синтаксис Javascript внутри кода JSX, вам нужно обернуть ваш Javascript в фигурные скобки.

row = () => {
   var rows = [];
   for (let i = 0; i<numrows; i++) {
       rows.push(<ObjectRow/>);
   }
   return rows;
}
<tbody>
{this.row()}  
</tbody>

Ответ 26

Я использую это как

<tbody>
  { numrows ? (
     numrows.map(obj => { return <ObjectRow /> }) 
    ) : null
  }
</tbody>

Ответ 27

Вот пример из документа React:Выражения JavaScript как дети

function Item(props) {
  return <li>{props.message}</li>;
}

function TodoList() {
  const todos = ['finish doc', 'submit pr', 'nag dan to review'];
  return (
    <ul>
      {todos.map((message) => <Item key={message} message={message} />)}
    </ul>
  );
}

как ваш случай, я предлагаю написать так:

function render() {
  return (
    <tbody>
      {numrows.map((roe, index) => <ObjectRow key={index} />)}
    </tbody>
  );
}

Обратите внимание, что ключ очень важен, потому что React использует ключ для различения данных в массиве

Ответ 28

Отличный вопрос.

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

Определите функцию, которая возвращает JSX:

const myExample = () => {
    let myArray = []
    for(let i = 0; i<5;i++) {
        myArray.push(<MyComponent/>)
    }
    return myArray
}

//... in JSX

<tbody>
    {myExample()}
</tbody>

Ответ 29

Вы также можете использовать функцию самозапускания:

return <tbody>
           {(() => {
              let row = []
              for (var i = 0; i < numrows; i++) {
                  row.push(<ObjectRow key={i} />)
              }
              return row

           })()}
        </tbody>

Ответ 30

Вы можете сделать что-то вроде:

let foo = [1,undefined,3]
{ foo.map(e => !!e ? <Object /> : null )}