You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React创建动态表格遇异常错误,求正确实现方案

React表格DOM嵌套验证问题修复

问题场景

我在React中开发一个简单程序,将状态中的对象数组渲染为表格。初始渲染正常,但重新渲染时出现异常,控制台抛出两个DOM嵌套验证警告。

原始代码

import React from "react";
import ReactDOM from "react-dom";
import { useState } from "react";
import "./style.css";

function Trains() {
  const [trainsList, setTrainsList] = useState([
    { name: "Thomas", destination: "Boston" },
    { name: "Duncan", destination: "New York" },
  ]);

  return (
    <div>
      <table>
        <thead>name</thead>
        <thead>destination</thead>

        {trainsList.map((train) => (
          <tbody key={train.name}>
            <td>{train.name}</td>
            <td>{train.destination}</td>
          </tbody>
        ))}
      </table>
      <br />
      Train Name: <input type="text" id="trainName" />
      <br />
      Destination: <input type="text" id="trainDest" />
      <br />
      <button
        onClick={() =>
          setTrainsList((prev) =>
            prev.push({ name: "Dennis", destination: "Denville" })
          )
        }
      >
        Submit
      </button>
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Trains />);

控制台警告

bundle.js:7294 Warning: validateDOMNesting(...): Text nodes cannot appear as a child of <thead>.
    at thead
    at table
    at div
    at Trains (http://localhost:3000/static/js/bundle.js:32:86)
bundle.js:7294 Warning: validateDOMNesting(...): <td> cannot appear as a child of <tbody>.
    at td
    at tbody
    at table
    at div
    at Trains (http://localhost:3000/static/js/bundle.js:32:86)

问题分析与修复方案

  1. 标签使用错误:HTML规范中,<thead>内部必须包含<tr>(表格行),再在<tr>里放置<th>(表头单元格),不能直接将文本作为<thead>的子元素。
  2. 与嵌套错误:<tbody>的直接子元素必须是<tr>,<td>(表格数据单元格)必须放在<tr>内部,不能直接作为<tbody>的子元素。
  3. 状态更新方式错误:push方法会直接修改原状态数组,违反React状态不可变原则,应该使用扩展运算符或concat创建新数组来更新状态。

修正后的完整代码

import React from "react";
import ReactDOM from "react-dom";
import { useState } from "react";
import "./style.css";

function Trains() {
  const [trainsList, setTrainsList] = useState([
    { name: "Thomas", destination: "Boston" },
    { name: "Duncan", destination: "New York" },
  ]);

  const handleAddTrain = () => {
    // 若需要读取输入框值,可在此处获取后传入新对象
    setTrainsList(prev => [...prev, { name: "Dennis", destination: "Denville" }]);
  };

  return (
    <div>
      <table>
        <thead>
          <tr>
            <th>name</th>
            <th>destination</th>
          </tr>
        </thead>
        <tbody>
          {trainsList.map((train) => (
            <tr key={train.name}>
              <td>{train.name}</td>
              <td>{train.destination}</td>
            </tr>
          ))}
        </tbody>
      </table>
      <br />
      Train Name: <input type="text" id="trainName" />
      <br />
      Destination: <input type="text" id="trainDest" />
      <br />
      <button onClick={handleAddTrain}>
        Submit
      </button>
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Trains />);

说明

  • 表头部分:合并了多余的<thead>标签,用单个<thead>包裹<tr>和<th>,符合HTML结构规范。
  • 表格内容:将每个数据项的<td>放在<tr>中,所有行统一放在一个<tbody>下(如需拆分多个<tbody>,每个内部仍需以<tr>作为直接子元素)。
  • 状态更新:使用扩展运算符创建新数组,保证状态更新的不可变性,避免React渲染异常。

内容的提问来源于stack exchange,提问作者DennisM

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 16:10:25