【问题标题】:Loop inside React JSX在 React JSX 中循环
【发布时间】:2016-10-16 21:11:24
【问题描述】:

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

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

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

【问题讨论】:

  • 请务必注意,在 JSX 中,您的 JavaScript 语法需要 { } 标签。这可能会帮助facebook.github.io/react/docs/…
  • let todos = this.props.todos.map((todo) =&gt; {return &lt;h1&gt;{todo.title}&lt;/h1&gt;})
  • @OverCoder 为什么要将整个返回放入 {} 标记中 => 返回

    {todo.title}

    不是吗?
  • @pravinpoudel 实际上这个答案很旧,更像let todos = this.props.todos.map(t =&gt; &lt;h1&gt;{t.title}&lt;/h1&gt;) :)

标签: javascript reactjs 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 are adding 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】:

    我不确定这是否适合您的情况,但通常map 是一个很好的答案。

    如果这是您的带有 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>
    

    【讨论】:

    • 如果迭代对象是数组,则映射更有意义;如果是数字,则 for 循环是合适的。
    • &lt;tbody&gt;{objects.map((o, i) =&gt; &lt;ObjectRow obj={o} key={i}/&gt;}&lt;/tbody&gt; 使用 Reactify 的 ES6 支持或 Babel。
    【解决方案3】:

    如果您还没有像@FakeRainBrigand 的答案那样的map() 数组,并且想要内联它,以便源布局对应的输出比@SophieAlpert 的答案更接近:

    使用 ES2015 (ES6) 语法(展开和箭头函数)

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

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

    Re: 使用 Babel 进行编译,其caveats page 表示传播需要Array.from,但目前(v5.8.23)在传播实际Array 时似乎并非如此。我有一个documentation issue 来澄清这一点。但是使用 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,您可以将其作为使用spread 的替代方法,如上图所示:

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

    (我认为您实际上可以省略 fill() 的任何参数,但我不是 100% 的。)感谢 @FakeRainBrigand 纠正了我在早期版本的 fill() 解决方案中的错误(请参阅修订版) .

    key

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

    【讨论】:

    • 请不要仅仅为了从 0..N 迭代而创建一个数组。这是 Javascript 的巅峰之作。
    【解决方案4】:

    简单地使用 ma​​p Array 方法和 ES6 语法:

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

    不要忘记key 属性。

    【讨论】:

    • 我添加了一个随机的“Math.random()”键而不是 id,当 setState 在儿童中时它不能正常工作,知道为什么吗?
    【解决方案5】:

    使用 Array map 函数是一种非常常见的方法,可以在 React 中循环遍历 Array 元素并根据它们创建组件。这是一种非常有效的循环方式,并且是一种在 JSX 中执行循环的整洁方式。这不是唯一的方法,而是首选的方法。

    此外,不要忘记根据需要为每次迭代设置一个唯一键ma​​p函数从0开始创建唯一索引,但不建议使用生成的索引,但如果你的值是唯一的或者有唯一的键,你可以使用它们:

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

    另外,如果您不熟悉 Array 上的 map 函数,请参考 MDN 中的几行代码:

    map 为每个元素调用一次提供的回调函数 数组,按顺序排列,并根据结果构造一个新数组。打回来 仅对具有赋值的数组索引调用, 包括未定义。它不要求缺少的元素 数组(即从未设置过的索引, 已删除或从未被赋值)。

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

    如果向地图提供了 thisArg 参数,它将被用作 回调的 this 值。否则,值 undefined 将用作 它的这个值。回调最终可观察到的这个值是 根据通常的规则确定 通过函数。

    map 不会改变调用它的数组(尽管 回调,如果被调用,可能会这样做)。

    【讨论】:

    • Array#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}>repeated 3 times</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】:

        有多种方法可以做到这一点。 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() 每个对象都指向一个&lt;ObjectRow&gt; 组件:

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

        【讨论】:

          【解决方案9】:

          您可能想查看React Templates,它允许您在 React 中使用 JSX 样式的模板,并带有一些指令(例如 rt-repeat)。

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

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

          【讨论】:

            【解决方案10】:

            如果numrows是一个数组,那很简单:

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

            React 中的数组数据类型要好得多。一个数组可以支持一个新的数组,并且支持filter、reduce等。

            【讨论】:

              【解决方案11】:

              如果您选择在 render 方法的 return() 内进行转换,最简单的选择是使用 ma​​p( ) 方法.使用 map() 函数将数组映射为 JSX 语法,如下所示(使用 ES6 语法)。


              父组件内部

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

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

              警告:数组或迭代器中的每个子元素都应该有 一个独特的“关键”道具。

              注意: 人们常犯的一个错误是在迭代时使用index 作为键。使用元素的index 作为键是一种反模式,您可以阅读更多关于它的信息here。简而言之,如果它不是静态列表,则永远不要使用index 作为键。


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

              ObjectRow 组件内部

              const { object } = this.props
              

              或者

              const object = this.props.object
              

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


              参考资料:

              map() method in JavaScript

              ECMAScript 6 or ES6

              【讨论】:

                【解决方案12】:

                有几个答案指向使用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 codefeatures fixture is listed here

                【讨论】:

                • 在处理 JSON 数据时,例如使用 fetch API 从数据库中检索数据时,我依赖于使用 Array.prototype.map 方法。这是一种方便且经过验证的方法。
                【解决方案13】:

                循环多次返回,可以借助frommap实现:

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

                在哪里i = number of times


                如果你想为渲染的组件分配唯一的键 ID,你可以使用React.Children.toArrayReact documentation 中的建议

                React.Children.toArray

                以平面数组的形式返回子级不透明数据结构,并为每个子级分配键。如果您想在渲染方法中操作子元素的集合,特别是如果您想在传递 this.props.children 之前对其进行重新排序或切片,则很有用。

                注意:

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

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

                【讨论】:

                  【解决方案14】:

                  假设我们在您的状态中有一系列项目:

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

                  【讨论】:

                  • 我认为您可能需要删除地图(项目)之后的 {},这似乎对我有用。
                  【解决方案15】:

                  ECMAScript 2015 / Babel 的可能性是使用生成器函数来创建 JSX 数组:

                  function* jsxLoop(times, callback)
                  {
                      for(var i = 0; i < times; ++i)
                          yield callback(i);
                  }
                  
                  ...
                  
                  <tbody>
                      {[...jsxLoop(numrows, i =>
                          <ObjectRow key={i}/>
                      )]}
                  </tbody>
                  

                  【讨论】:

                    【解决方案16】:

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

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

                    【讨论】:

                      【解决方案17】:

                      ES2015 Array.from带map函数+key

                      如果您对.map() 没有任何内容,您可以使用Array.from()map 函数来重复元素:

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

                      【讨论】:

                        【解决方案18】:

                        我用这个:

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

                        PS:千万不要忘记钥匙,否则你会收到很多警告!

                        【讨论】:

                        • 如果您的项目没有.Id 属性,例如items.map( (item, index) =&gt; &lt;Foo key={index}&gt;{item}&lt;/Foo&gt;,则使用数组索引
                        【解决方案19】:

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

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

                        2. 循环进入return

                          方法一 让容器 = []; let arr = [1, 2, 3] //可以是任何数组,对象

                          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 选择元素。但是,我更有可能将它们替换为 Array.prototype.map() 方法,因为 map 方法看起来更紧凑(代码更少)。
                        【解决方案20】:

                        只需使用.map() 循环您的集合并返回&lt;ObjectRow&gt; 项目以及每次迭代的道具。

                        假设objects 是某处的数组...

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

                        【讨论】:

                          【解决方案21】:

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

                          所以我可能会这样做:

                          import _ from 'lodash';
                          
                          ...
                          
                          const TableBody = ({ objects }) => {
                            const objectRows = objects.map(obj => <ObjectRow object={obj} />);      
                          
                            return <tbody>{objectRows}</tbody>;
                          } 
                          

                          诚然,这是一个如此少量的代码,内联它可能工作得很好。

                          【讨论】:

                            【解决方案22】:

                            您当然可以按照其他答案的建议使用 .map 解决。如果您已经使用Babel,您可以考虑使用jsx-control-statements

                            它们需要一些设置,但我认为它在可读性方面是值得的(尤其是对于非 React 开发人员)。 如果你使用 linter,还有eslint-plugin-jsx-control-statements

                            【讨论】:

                              【解决方案23】:

                              这里有一个简单的解决方案。

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

                              不需要映射和复杂的代码。您只需要将行推送到数组并返回值以呈现它。

                              【讨论】:

                              • 在创建 html select 元素时,我首先想到了这种类似的技术,所以我使用了它,尽管我使用了 forEach() 方法。但是当我重新阅读有关列表和键主题的 React 文档时,我发现他们使用了 map() 方法,这里的几个答案也显示了这一点。这让我认为这是首选方式。我同意,因为它看起来更紧凑(代码更少)。
                              【解决方案24】:

                              您的 JSX 代码将编译为纯 JavaScript 代码,任何标签都将替换为 ReactElement 对象。在 JavaScript 中,你不能多次调用一个函数来收集它们返回的变量。

                              这是非法的,唯一的办法就是用数组来存储函数返回的变量。

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

                              也许我们可以编写其他编译器来重新创建新的 JSX 语法来实现重复函数,就像 Angular's ng-repeat

                              【讨论】:

                                【解决方案25】:

                                这是来自 React 文档的示例,JavaScript Expressions as Children

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

                                请注意 key 非常重要,因为 React 使用 key 来区分数组中的数据。

                                【讨论】:

                                  【解决方案26】:

                                  由于您是在 JSX 代码中编写 JavaScript 语法,因此您需要将 JavaScript 代码用大括号括起来。

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

                                  【讨论】:

                                    【解决方案27】:

                                    你可以在 React for 循环中使用 .map()。

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

                                    【讨论】:

                                      【解决方案28】:

                                      你也可以使用自调用函数:

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

                                      【讨论】:

                                      • 在渲染中使用匿名函数在 React 中不被认为是一种好的做法,因为这些函数需要在每次重新渲染时重新创建或丢弃。
                                      • @VlatkoVlahek 您误认为每个渲染周期都会重新创建函数道具,这可能导致大规模性能下降。在其他任何地方创建匿名函数不会对性能产生任何重大影响。
                                      • @EmileBergeron 这是前一阵子哈哈。我同意如果这不用作道具,没有区别。
                                      【解决方案29】:

                                      我喜欢用它

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

                                      【讨论】:

                                        【解决方案30】:

                                        即使是这段代码也能完成同样的工作。

                                        <tbody>
                                           {array.map(i => 
                                              <ObjectRow key={i.id} name={i.name} />
                                           )}
                                        </tbody>
                                        

                                        【讨论】:

                                        • 解释一下。
                                        猜你喜欢
                                        • 2018-05-17
                                        • 2020-10-05
                                        • 2017-07-24
                                        • 2019-01-13
                                        • 2020-11-15
                                        • 2019-03-10
                                        • 2020-07-19
                                        相关资源
                                        最近更新 更多