如何包装返回多个表行的 React 组件并避免“<tr> 不能作为 <div> 的子级出现”错误?

Posted

技术标签:

【中文标题】如何包装返回多个表行的 React 组件并避免“<tr> 不能作为 <div> 的子级出现”错误?【英文标题】:How do I wrap a React component that returns multiple table rows and avoid the "<tr> cannot appear as a child of <div>" error? 【发布时间】:2016-10-25 06:28:22 【问题描述】:

我有一个名为 OrderItem 的组件,它接受一个内部包含多个对象(至少两个)的对象,并将它们呈现为表中的多行。表内将有多个 OrderItem 组件。问题是在组件的渲染函数中,我不能返回多行。我只能返回一个组件,如果我将它们包装在一个 div 中,它会显示“&lt;tr&gt; 不能作为&lt;div&gt; 的子组件出现”

代码看起来像这样(为了便于阅读,我省略了一些内容)

Parent() 
  render() 
    return (
      <table>
        <tbody>
          
            _.map(this.state.orderItems, (value, key) => 
              return <OrderItem value=value myKey=key/>
            )
          
        </tbody>
      </table>
    )
  


class OrderItem extends React.Component 
  render() 
    return (
      <div> // <-- problematic div
        <tr key=this.props.myKey>
          <td> Table this.props.value[0].table</td>
          <td> Item </td>
          <td> Option </td>
        </tr>
        this.props.value.map((item, index) => 
          if (index > 0)  // skip the first element since it's already used above
            return (
              <tr key=this.props.myKey + index.toString()>
                <td><img src=item.image alt=item.name /> item.name</td>
                <td>item.selectedOption</td>
              </tr>
            )
          
        )
      </div>
    )
  

有没有一种方法可以返回这些多行并将它们放在同一个表中,而无需将它们包装在 div 中并出现错误?我意识到我可以为每个组件制作一个单独的表格,但这会使我的格式有点偏离。

【问题讨论】:

一个选项可能是删除*** tbody 标签。然后让 map 在一个 标签中返回。 这似乎与下面的解决方案非常相似,带有多个表格标签,只是使用 tbody 而不是 table。 我相信现在可以使用reactjs.org/docs/fragments.html 【参考方案1】:

一种方法是将OrderItem拆分为两个组件,将渲染逻辑移动到方法Parent.renderOrderItems中:

class Parent extends React.Component 
  renderOrderItems() 
    const rows = []
    for (let orderItem of this.state.orderItems) 
      const values = orderItem.value.slice(0)
      const headerValue = values.shift()
      rows.push(
        <OrderItemHeaderRow table=headerValue.table key=orderItem.key />
      )
      values.forEach((item, index) => 
        rows.push(
          <OrderItemRow item=item key=orderItem.key + index.toString() />
        )
      )
    
    return rows
  
  render() 
    return (
      <table>
        <tbody>
           this.renderOrderItems() 
        </tbody>
      </table>
    )
  


class OrderItemHeaderRow extends React.Component 
  render() 
    return (
      <tr>
        <td> Table this.props.table</td>
        <td> Item </td>
        <td> Option </td>
      </tr>
    )
  


class OrderItemRow extends React.Component 
  render() 
    const  item  = this.props
    return (
      <tr>
        <td>
          <img src=item.image alt=item.name />
          item.name
        </td>
        <td>
          item.selectedOption
        </td>
      </tr>
    )
  

【讨论】:

感谢您的回答。不过,将表格放在一个组件中似乎会不那么复杂。【参考方案2】:

似乎没有办法将它们包装干净,所以更简单的解决方案是将整个表格放在组件中,并有多个表格并弄清楚格式。

Parent() 
   render() 
       return (
           _.map(this.state.orderItems, (value, key) => 
               return <OrderItem value=value myKey=key key=key/>
           )
       )
   



class OrderItem extends React.Component 
    render() 
        return (
            <table>
                <tbody>
                   <tr>
                       <td> Table this.props.value[0].table</td>
                       <td> Item </td>
                       <td> Option </td>
                    </tr>
                    this.props.value.map((item, index) => 
                        if (index > 0)  // skip the first element since it's already used above
                            return (
                                <tr key=this.props.myKey + index.toString()>
                                    <td> <img src=item.image alt=item.name /> item.name</td>  
                                    <td>item.selectedOption</td>
                                </tr>
                            )
                        
                    )
                </tbody>
            </table>
        )
    

【讨论】:

【参考方案3】:

是的!可以将项目映射到表内的多个表行。一个不会引发控制台错误并且在语义上实际上是正确的解决方案是使用 tbody 元素作为根组件并根据需要填充尽可能多的行。

items.map(item => (
   <tbody>
       <tr>...</tr>
       <tr>...</tr>
   </tbody>
))

以下帖子涉及有关它的道德问题,并解释了为什么是的,我们可以使用多个 tbody 元素 Can we have multiple <tbody> in same <table>?

【讨论】:

据记录,这在当时似乎是个好主意,但在 react 16 中,我们可以而且应该使用片段。【参考方案4】:

这是一个老问题,但也许有人偶然发现了它。由于我还不能发表评论,这里是对@trevorgk 答案的一点补充:

我用它来渲染一个每个项目有多个行的表格(大约 1000 个项目导致大约 2000 行有 15 列),并注意到 Firefox 的性能非常糟糕(即使在 57 中)。

我有纯组件渲染每个项目(每个项目一个

,每个项目包含两行),每个项目包含一个(受控)复选框。

当点击复选框时,Firefox 需要十多秒才能更新 - 尽管由于纯组件而实际上只有一项更新。 Chrome 的更新最多用了半秒时间。

我切换到 React 16 并没有发现任何区别。然后我用了新的AWESOME!!!从组件的渲染函数返回一个数组并去掉 1000 个

元素的功能。 Chrome 的性能大致相同,而 Firefox 的更新“飙升”到大约半秒(与 Chrome 没有明显差异)

【讨论】:

【参考方案5】:

React 16 现在来救援了,您现在可以使用React.Fragment 来呈现元素列表,而无需将其包装到父元素中。你可以这样做:

render() 
  return (
    <React.Fragment>
      <tr>
        ...
      </tr>
    </React.Fragment>
  );

【讨论】:

根据我的经验,&lt;React.Fragment&gt; 似乎也比 &lt;tbody&gt; 好很多。【参考方案6】:

就我而言,解决方案是返回一个数组而不是一个片段:

    return [
        <TrHeader />,
        <TrRows />
    ];

【讨论】:

以上是关于如何包装返回多个表行的 React 组件并避免“<tr> 不能作为 <div> 的子级出现”错误?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用单个查询获取存储在不同变量中的多个表行的计数?

如何从特定表行的 jQuery 数据表中访问单元格

使用多个 HOC 包装器导出 React 组件?

如何使用 react.js 动态生成表行

如何在 PHP 中向表行添加多个类? [关闭]

FragmentContainer 与仅作为包装器的另一个 React 组件?