简体   繁体   English

如何包装返回多个表行的 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?

I have a component called OrderItem that takes an object with multiple objects (at least two) inside it, and renders them as multiple rows inside a table.我有一个名为 OrderItem 的组件,它接受一个包含多个对象(至少两个)的对象,并将它们呈现为表中的多行。 There will be multiple OrderItem components inside the table.表内将有多个 OrderItem 组件。 The problem is that in the component's render function, I can't return multiple lines.问题是在组件的渲染函数中,我不能返回多行。 I can only return a single component, and if I wrap them in a div, it says " <tr> cannot appear as a child of <div> "我只能返回一个组件,如果我将它们包装在一个 div 中,它会显示“ <tr>不能作为<div>的子项出现”

The code looks something like this (I left some stuff out for easier readability)代码看起来像这样(为了更容易阅读,我省略了一些东西)

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} width="50"/> {item.name}</td>
                <td>{item.selectedOption}</td>
              </tr>
            )
          }
        })}
      </div>
    )
  }
}

Is there a way I can return those multiple rows and have them be in the same table without wrapping them in a div and getting an error?有没有办法可以返回这些多行并将它们放在同一个表中,而不会将它们包装在 div 中并出现错误? I realize I can make a separate table for each component, but that throws my formatting off a bit.我意识到我可以为每个组件制作一个单独的表格,但这会使我的格式有点偏离。

React 16 is now here to rescue, you can now use React.Fragment to render list of elements without wrapping it into a parent element. React 16现在来拯救了,您现在可以使用React.Fragment来渲染元素列表,而无需将其包装到父元素中。 You can do something like this:你可以这样做:

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

Yes!!是的!! It is possible to map items to multiple table rows inside a table.可以将项目映射到表内的多个表行。 A solution which doesn't throw console errors and semantically is actually correct, is to use a tbody element as the root component and fill with as many rows as required.一个不会抛出控制台错误并且语义上实际上是正确的解决方案是使用tbody元素作为根组件并根据需要填充尽可能多的行。

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

The following post deals with the ethical questions about it and explains why yes we can use multiple tbody elements Can we have multiple <tbody> in same <table>?下面的帖子涉及有关它的道德问题,并解释了为什么我们可以使用多个tbody元素我们可以在同一个 <table> 中有多个 <tbody> 吗?

One approach is to split OrderItem into two components, moving the rendering logic into a method Parent.renderOrderItems :一种方法是将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} width="50"/>
          {item.name}
        </td>
        <td>
          {item.selectedOption}
        </td>
      </tr>
    )
  }
}

It seems there is no way to wrap them cleanly, so the easier solution is to just put the whole table in the component and just have multiple tables and figure out the formatting.似乎没有办法干净地包装它们,所以更简单的解决方案是将整个表格放在组件中,并且只有多个表格并弄清楚格式。

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} width="50"/> {item.name}</td>  
                                    <td>{item.selectedOption}</td>
                                </tr>
                            )
                        }
                    })}
                </tbody>
            </table>
        )
    }
}

It is an old question, but maybe someone stumbles on it.这是一个古老的问题,但也许有人偶然发现了它。 Since I cannot comment yet, here is a little addition to the answer of @trevorgk:由于我还不能发表评论,这里是对@trevorgk 的回答的补充:

I used this to render a table with multiple rows per item (about 1000 items resulting in about 2000 rows with 15 columns) and noticed really bad performance with Firefox (even in 57).我用它来渲染一个每项有多行的表(大约 1000 个项目导致大约 2000 行和 15 列)并注意到 Firefox 的性能非常差(即使在 57 中)。

I had pure components rendering each item (one <body> per item containing two rows each) and each item contained a (controlled) checkbox.我有纯组件渲染每个项目(每个项目一个 <body>,每个项目包含两行),每个项目包含一个(受控)复选框。

When clicking the checkbox Firefox took more than ten seconds to update - although only one item was actually updated due to pure components.当单击复选框时,Firefox 花了十多秒钟来更新 - 尽管由于纯组件,实际上只更新了一项。 Chrome's update took at most half a second. Chrome 的更新最多只用了半秒。

I switched to React 16 and I noticed no difference.我切换到 React 16,我发现没有区别。 Then I used the new AWESOME!!!然后我用了新的AWESOME!!! feature of returning an array from a component's render function and got rid of the 1000 <tbody> elements.从组件的渲染函数返回数组的功能,并摆脱了 1000 个 <tbody> 元素。 Chrome's performance was approximately the same while Firefox's "skyrocketed" to about half a second for an update (no perceived difference to Chrome) Chrome 的性能大致相同,而 Firefox 的更新“猛增”到大约半秒(与 Chrome 没有明显差异)

In my case, the solution was to return an array instead of a fragment:就我而言,解决方案是返回一个数组而不是一个片段:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM