标签仅为布局容器,无数据语义;数据行需由 包裹并配合 / 构成;禁止在 中嵌套 ,须用 内部布局;colspan/rowspan 影响视觉列数但不改变 tr.cells.length。

TR 标签本身不定义数据记录,只是容器
<tr> 是 HTML 表格中纯粹的布局结构标签,它不携带语义含义,也不表示“一行数据记录”。浏览器和辅助技术不会把它当作数据单元处理。真正定义数据行语义的是 <code><tbody> + <code><tr> 的组合,且需配合 <code><th>(表头)或 <code><td>(数据单元格)才能构成可被解析的行式结构。
<p>常见错误是把 <code><tr> 当作数据模型层的“record”来操作,比如在 JS 中直接给 <code><tr> 加 <code>data-id="123" 就以为完成了数据绑定——这能用,但不可靠:若后续用 aria-rowindex 或表格导出工具(如 SheetJS)读取,缺失 <thead>/<code><tbody> 区分会导致索引错位。
<ul><li>必须将数据行包裹在 <code><tbody> 内,表头放在 <code><thead>,否则 <code>table.rows 和 table.tBodies[0].rows 返回的 NodeList 顺序可能不一致
<tr> 上写 <code>role="row" —— 原生 <tr> 已有该 role,重复声明会干扰屏幕阅读器
<li>如果要用 JS 动态生成行,优先用 <code>document.createElement('tr') + appendChild(),而非拼接字符串后 innerHTML 插入,否则事件委托失效、<td> 内的 <code><input> 状态丢失
水平单元格必须用 TD 或 TH,不能用 DIV 塞进 TR
HTML 规范明确限定:<tr> 的合法子元素只有 <code><td>、<code><th>、<code><template></template> 和空白字符。把 <div> 直接塞进 <code><tr> 会导致浏览器自动纠错:DOM 中出现隐式 <code><tbody>、<code><tr> 被拆开、甚至整行消失(尤其在 Safari 中)。
<p>典型报错现象:<code>console.log(tr.children) 返回空集合,但 tr.innerHTML 看起来有内容——其实是浏览器把非法结构重排成了 <table><tbody><tr><td><div>...</div></td></tr></tbody></table> 这种嵌套。
- 所有横向单元格一律用
<td>(数据)或 <code><th>(列头),哪怕只是展示文字 <li>需要复杂布局?在 <code><td> 内部嵌套 <code><div>、<code><span></span>或 Flex 容器,而不是替换<td> <li>用 CSS 控制单元格宽度时,优先设 <code>table { table-layout: fixed }+td { width: 120px },避免依赖内容撑开导致列宽错乱 - 需要精确列数匹配时,先从
<thead> 的第一行算出总列数(遍历 <code>th并累加colSpan),再逐行校验 -
rowspan更危险:跨行单元格在后续<tr> 中不会再次出现,但逻辑上仍占位——JS 遍历时若没跳过“空位置”,容易误判数据归属列 <li>避免嵌套 <code>colspan+rowspan,Chrome 和 Firefox 对双重跨单元格的渲染行为不完全一致 - SSR 场景下,
<tr> 没有 <code>innerHTML属性(它是 HTMLElement,不是 Element),想动态插入内容得用insertAdjacentHTML或append() - 用
getComputedStyle(tr).display === 'none'判断隐藏比检查tr.style.display更准,因为后者不反映 CSS 类或父级影响
表格行的语义边界其实很薄:它既不是数据实体,也不是样式终点。真正关键的是
TR 的 colspan/rowspan 影响行内单元格逻辑数量
colspan 和 rowspan 不改变 <tr> 的 DOM 子元素个数,但会改变其“视觉占据的列数”。这意味着:用 <code>tr.cells.length 获取单元格数时,结果可能小于表格实际列数;而用 tr.querySelectorAll('td, th').length 也一样——它只数显式写的单元格,不管 colspan。
例如:<tr>
<td colspan="2">A</td>
<td>B</td>
</tr> 在 3 列表格中,tr.cells.length === 2,但视觉上占满 3 列。做列对齐、导出 CSV 或键盘导航(如 Tab 键跳转)时,这个差异常被忽略。
服务端渲染或框架中,TR 不该承担状态管理职责
React/Vue 中常见反模式:把整行数据挂载到 <tr key="{id}"> 上,然后在 <code>onClick 里从 event.currentTarget 取 dataset。这看似方便,实则破坏了组件粒度和可测试性——点击区域和数据耦合太紧,无法单独复用单元格逻辑,也无法响应键盘空格/回车触发。
更可靠的方式是把数据下放到每个 <td> 或内部按钮,并用事件委托收口:
<pre class="brush:php;toolbar:false;">tbody.addEventListener('click', e => {
const td = e.target.closest('td');
if (td && td.dataset.field === 'status') {
// 处理状态列点击
}
});</pre>
<ul><li>不要在 <code><tr> 上绑定 <code>v-model 或 useState,状态应属于行对应的数据对象,而非 DOM 节点
<tbody> 的存在、<code><td> 的合规使用,以及跨行属性带来的隐式列偏移——这些地方不显眼,但一出问题就难定位。</td>











