在React JSX内部循环


1277

我正在React中尝试执行以下操作JSX(其中ObjectRow是一个单独的组件):

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

我意识到并理解为什么这是无效的JSX,因为它JSX映射到函数调用。但是,由于来自模板领域并且是的新手JSX,所以我不确定如何实现上述目标(多次添加组件)。


38
请务必注意,在JSX中,您需要在JavaScript语法周围加上{}标签。这可能对facebook.github.io/react/docs/…有所帮助。
以赛亚·格雷

30
let todos = this.props.todos.map((todo) => {return <h1>{todo.title}</h1>})
OverCoder


@OverCoder为什么将整个return放入{}标记中,这将是=> return <h1> {todo.title} </ h1>是吗?
Pravin Poudel

1
@pravinpoudel实际上答案是旧的,更像是let todos = this.props.todos.map(t => <h1>{t.title}</h1>):)
OverCoder

Answers:


1202

就像您只是在调用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的工作方式。


3
可能编译器已更改,因为此示例似乎未在jsx编译器中进行编译,但是像在FakeRainBrigand的下面的答案中那样使用map似乎可以正确编译。
2014年

9
我可以保证最后一个示例仍然可以在最新的JSX编译器上正确编译。
索菲·阿尔珀特

3
这很好。FakeRainBrigand的答案适用于简单的数组迭代,但是对于不能使用的情况map(例如,变量中没有存储集合),此答案是必须的。
开尔文

52
React需要有效地更新dom,在这种情况下,父级应该key为每个子级分配一个dom。参考:facebook.github.io/react/docs/…–
qsun

7
这个例子是可行的,但是它错过了一些重要的key属性,例如属性,以及在React代码库中并未真正使用的代码样式。请将此答案stackoverflow.com/questions/22876978/loop-inside-react-jsx/…视为正确答案。
okonetchnikov '16

865

不知道这是否适合您的情况,但通常使用地图是个不错的选择。

如果这是带有for循环的代码:

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

您可以使用map这样写:

<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>

43
如果iteratee是数组,则Map更有意义。如果为数字,则for循环是合适的。
代码窃窃私语

26
<tbody>{objects.map((o, i) => <ObjectRow obj={o} key={i}/>}</tbody>使用Reactify的ES6支持或Babel。
Distortum

3
+1。我什至将它与ul(无序列表)一起使用。<ul> {objects.map((object:string,i:number)=>{ return <li>{object}</li> })} </ul>使用TypeScript
Roberto C Navarro

5
这很棒,但是您不能映射到对象上。.为此,我会使用for..in
Damon

4
请记住,使用Object.keys时,键的顺序是任意的。我发现单独存储键的顺序效果很好,如果需要,还可以存储对象文字的顺序。这使我可以编写类似this.props.order.map((k)=><ObjectRow key={k} {...this.props.items[k]} />)
tgrrr

443

如果您还没有map()喜欢@FakeRainBrigand的答案的数组,并且想要内联它,那么源布局将比@SophieAlpert的答案更接近输出:

使用ES2015(ES6)语法(扩展和箭头功能)

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

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

回复:使用Babel进行转译时,其警告页面说这Array.from是传播所必需的,但目前(v5.8.23)在传播实际值时似乎并非如此Array。我有一个文档问题需要澄清。但是使用后果自负或使用polyfill。

香草ES5

Array.apply

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

内联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

在所有情况下,keyattr都会减轻开发版本的警告,但在子级中无法访问。如果希望子级中的索引可用,则可以传递一个额外的attr。请参阅列表和键进行讨论。


2
yield呢 当我们有生成器时,将元素推入中间数组是很丑陋的。做他们的工作?
mpen 2015年

2
@Mark我不知道生成器在这里真的适用。在JSX上下文中,此操作的关键是返回数组的表达式。因此,如果您打算以某种方式使用生成器,则无论如何都可能会对其进行扩展,并且它可能比此处基于ES2015的解决方案更为冗长。
JMM

2
如何为这个更详细?为什么JSX不接受任何可迭代的方法,而不仅仅是数组?
mpen

2
@马克那的确是比(ES5)IIFE例如我用冗长,但它是相当多的详细比[...Array(x)].map(() => <El />))的版本,我认为这是最优雅的,为什么我特色吧。回复:第二个问题,这很重要。关于JSX的似乎没有什么可以排除它的,所以这取决于React或转换后的JSX的另一个使用者对子级参数的处理方式,而我不看源代码就不能说。你知道吗?这是一个有趣的问题。
JMM

3
@corysimmons很酷,感谢您的信息fill()。现在,我记得我犹豫的原因是关于ES规范中如何指示参数的可选性的问题(必须调查一下)。逗号只是删除数组元素的一种方法-在本例中为第一个。如果希望索引从要散布的数组的1..length开始,而不是从散布的数组的0..length-1开始,则可以执行此操作(因为省略的元素仅会影响数组的长度,所以不要具有相应的属性)。
JMM

85

只需使用带有ES6语法的map Array方法即可:

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

不要忘记key财产。


我添加了一个随机的'Math.random()'密钥而不是id,当在孩子内部使用setState时,它不能正常工作,知道为什么吗?
奥利弗·D

82

使用Array map函数是在React中循环遍历元素数组并根据它们创建组件的非常常见的方法,这是一种很好的循环方法,在JSX中循环是非常有效和整洁的方法,这不是唯一的方法的方法,但首选的方法。 另外,不要忘记根据需要为每个迭代都拥有唯一的KeyMap函数从0开始创建一个唯一索引,但是不建议使用产生的索引,但是如果您的值是唯一的或存在唯一键,则可以使用它们:

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

另外,如果您不熟悉Array上的map函数,则从MDN出发几行:

map依次为数组中的每个元素调用提供的回调函数,然后从结果中构造一个新的数组。仅对具有已分配值(包括未定义)的数组索引进行调用。不会为缺少数组的元素(即从未设置,已删除或从未分配值的索引)调用它。

使用三个参数调用callback:元素的值,元素的索引以及要遍历的Array对象。

如果提供thisArg参数来映射,它将用作回调的this值。否则,未定义的值将用作此值。回调最终可观察到的该值是根据确定函数所看到的值的常规规则确定的。

map不会使在其上被调用的数组发生变化(尽管如果调用了回调,则可能会这样做)。


Array#map不是异步的!
philraj

52

如果您已经在使用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>
        );
    }
}

1
如果您不包括lodash之类的库,则Array.prototype.map可能是一个不错的选择。developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/…
以赛亚·格雷

1
@IsaiahGrey仅当您已经有数组时。有时您只想重复几次。
mpen

1
当然可以!可以根据实际数据确定很多不同的方法。我发现在我们的开发过程中,由于建模的方式,我们经常能够适应地图。使用lodash的好例子!顺便说一句,您是否正在使用属性初始化程序语法将方法绑定到组件?
以赛亚·格雷

4
@IsaiahGrey我认为它们只是被称为ES6 方法定义
mpen

41

您还可以提取return块之外的内容:

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

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

34

我知道这是一个旧线程,但是您可能想检出React模板,它确实允许您在React中使用jsx样式的模板,并带有一些指令(例如rt-repeat)。

如果使用react-templates,那么您的示例将是:

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

为什么当普通的javascript已经提供了带有各种for循环或Array.prototype.map方法的解决方案来解决问题时,为什么使用附加库?我本人已经在我当前的项目中使用了两种简单的javascript方式,都很好用。尽可能使用简单的javascript方法的习惯有助于我提高javascript的技能。我仅在似乎难以解决某些问题(例如使用React-Router进行路由)的解决方案时使用其他库。
Lex Soft

27

有多种方法可以做到这一点。JSX最终会编译为JavaScript,因此,只要您编写有效的JavaScript,就可以了。

我的答案旨在巩固此处已经介绍的所有奇妙方法:

如果没有对象数组,则只需行数:

return块中,创建一个Array并使用Array.prototype.map

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

return块外,只需使用常规的JavaScript for循环:

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块外,只需使用常规的JavaScript for循环:

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

2
好的答案:各种技术都只使用普通的javascript来显示javascript的功能,这要归功于其执行类似任务的各种方式。
Lex Soft

24

如果numrows是一个数组,则非常简单。

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

React中的数组数据类型更好,数组可以支持新数组,并支持过滤,缩小等。


这不是一个体面的答案,因为它没有使用键。
kojow7

21

有几个答案指向使用该map语句。这是一个完整的示例,在FeatureList组件中使用迭代器以基于称为features的JSON数据结构列出Feature组件。

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

您可以在GitHub上查看完整的FeatureList代码。该功能夹具这里列出


当处理JSON数据时,例如使用fetch API从数据库中检索数据时,我依靠使用Array.prototype.map方法。这是一种方便且经过验证的方法。
Lex Soft

17

要循环的次数和回报,你可以的帮助下实现它frommap

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

哪里 i = number of times


如果您想在渲染的组件中分配唯一的密钥ID,则可以React.Children.toArray按照React文档中的建议使用

React.Children.toArray

以分配给每个子项的键的平面数组形式返回子项不透明数据结构。如果要在渲染方法中操纵子级集合,则很有用,特别是如果要在传递它之前对this.props.children重新排序或切片。

注意:

React.Children.toArray()展平子列表时,更改键以保留嵌套数组的语义。也就是说,toArray在返回的数组中的每个键之前添加前缀,以便每个元素的键的作用域限定于包含它的输入数组。

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

17

如果您选择这个转换内部回报率()渲染方法,最简单的办法是使用地图()方法。使用map()函数将数组映射为JSX语法,如下所示(使用ES6语法)。


内部父组件

<tbody>
   { objectArray.map(object => <ObjectRow key={object.id} object={object.value}>) }
</tbody>

请注意key添加到您的子组件的属性。如果未提供键属性,则可以在控制台上看到以下警告。

警告:数组或迭代器中的每个子代都应具有唯一的“键”道具。

注意:人们经常犯的一个错误是index在迭代时将其用作键index,将元素用作键是一种反模式,您可以在此处了解更多信息。简而言之,如果不是静态列表,则永远不要将其index用作键。


现在,在ObjectRow组件中,您可以从其属性访问该对象。

内部ObjectRow组件

const { object } = this.props

要么

const object = this.props.object

这应该获取您从父组件传递到ObjectRow组件中的变量object对象。现在,您可以根据自己的目的吐出该对象中的值。


参考文献:

Javascript中的map()方法

ECMA Script 6或ES6


16

假设我们在您的状态下有一系列商品:

[{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>

我认为您可能需要摆脱map(item)之后的{},这似乎对我有用。
伊恩

15

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>

15

...或者您也可以准备一个对象数组并将其映射到一个函数以获取所需的输出。我更喜欢这样做,因为它可以帮助我保持良好的编码实践,并且在返回的渲染过程中没有任何逻辑。

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

15

只需用于.map()遍历您的集合并<ObjectRow>从每次迭代中返回带有道具的物品。

假设objects某处有一个数组...

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

15

使用地图功能+键的ES2015 Array.from

如果您一无所有,则.map()可以使用Array.from()map函数重复元素:

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

15

我用这个:

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

PS:永远不要忘记钥匙,否则您会收到很多警告!


或者,如果您的商品没有.Id属性,则使用数组索引,例如items.map( (item, index) => <Foo key={index}>{item}</Foo>
kontur

13

我倾向于采用一种编程逻辑发生在的返回值之外的方法render。这有助于使实际呈现的内容易于理解。

所以我可能会做类似的事情:

import _ from 'lodash';

...

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

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

诚然,这是少量的代码,所以内联它可能会正常工作。


Lodash地图的忠实粉丝,用于遍历对象。我倾向于import { map } from 'lodash'这样做,即使您不需要它们也不导入所有lodash函数。
Stretch0

如今,我们甚至不需要lodash映射-只需直接在数组上映射即可。
亚当·多纳休

是的,但是您不能使用es6循环访问对象map(),只能使用数组。那就是我所说的lodash有益于循环对象。
Stretch0

我以为你的意思objects是我的错。
亚当·多纳休


12

您的JSX代码将编译为纯JavaScript代码,所有标签都将被ReactElement对象替换。在JavaScript中,不能多次调用函数来收集其返回的变量。

这是非法的,唯一的方法是使用数组存储函数返回的变量。

或者,您可以使用自JavaScript ES5以来Array.prototype.map可用的版本来处理这种情况。

也许我们可以编写其他编译器来重新创建新的JSX语法,以实现与Angularng-repeat一样的重复功能。


12

这可以通过多种方式来完成。

  1. 如上所述,在return将所有元素存储在数组中之前
  2. 内部循环 return

    方法一

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

    方法二

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

是的 在当前项目中,我使用方法1,即使用Array.prototype,forEach()方法创建HTML选择元素,该元素由数据库中的数据填充。但是,由于map方法看起来更紧凑(代码更少),因此我更可能将它们替换为Array.prototype.map()方法。
Lex Soft

10

这是一个简单的解决方案。

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

无需映射和复杂的代码。您只需要将行压入数组并返回值即可进行渲染。


在创建html select元素时,这种相似的技术首先是我想到的,因此尽管使用了forEach()方法,但还是使用了它。但是,当我重新阅读有关列表和键的主题的React文档时,我发现它们使用map()方法,如此处的几个答案所示。这使我认为这是首选方式。我同意,因为它看起来更紧凑(代码更少)。
Lex Soft

9

由于您是在JSX代码中编写Javascript语法,因此需要将Javascript括在花括号中。

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

9

这是React doc的示例: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使用密钥来区分数组中的数据。


9

我用它像

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


8

好问题。

当我想添加一定数量的组件时,我要做的就是使用一个辅助函数。

定义一个返回JSX的函数:

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

//... in JSX

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

8

您还可以使用自调用功能:

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

           })()}
        </tbody>

在render中使用匿名函数不被认为是一种好的响应做法,因为这些函数需要在每次重新渲染时重新创建或丢弃。
Vlatko Vlahek

@VlatkoVlahek您会误以为它会在每个渲染周期中重新创建功能道具,这可能会导致大规模的性能下降。在其他任何地方创建匿名函数都不会以任何重大方式影响性能。
Emile Bergeron

1
@EmileBergeron这是前一段时间哈哈。我同意如果不将其用作道具,没有区别。
Vlatko Vlahek
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.