语义标签是dom契约而非装饰,是js查询、无障碍检测、seo抓取的唯一稳定入口;需避免重复main、误用aside/section、深层嵌套、class命名耦合样式、html与js生命周期不同步等问题。

语义标签不是装饰,是DOM契约
在复杂单页应用(SPA)里,<main></main>、<nav></nav>、<section></section>这些标签不是“让代码看起来更现代”的选配,而是 JS 查询、无障碍检测、SEO 抓取的唯一稳定入口。你写 document.querySelector('main') 能稳拿主体区域,但 document.querySelector('.content-wrap') 一旦有人改 class 名或加个 content-wrap-v2,逻辑就断了。
常见错误现象:
- 多个
<main></main>出现在同一页面(比如路由切换后未销毁旧节点) - 把轮播图容器、商品筛选栏塞进
<aside></aside>——它本意是“与当前内容相关但可独立存在”,不是“右边那个盒子” - 用
<div data-role="header"> 替代 <code><header></header>,绕开语义却没补 ARIA role,导致屏幕阅读器跳过实操建议:
- 路由组件 mount 时检查是否已存在
<main></main>,有则移除或复用,避免重复插入 - 每个
<section></section>必须带<h2></h2>或更高层级标题,否则 Lighthouse 会报 “heading-levels” 失败 - 动态插入的模块(如弹窗、抽屉)用
data-module="modal-search"标记,比靠 class 更抗干扰
嵌套超三层?先问是不是结构错了
SPA 常见陷阱:一个
<div> 包着 <code><div> 再包 <code><div>,最后才到 <code><p></p>或<button></button>。这不是“视觉分层”,是 DOM 层级失控。Chrome DevTools 里点开一个按钮要展开四层才能看到文本节点,JS 查询慢、CSS 选择器易断裂、新人根本不敢动。性能影响很实在:DOM 节点数每增 1000,首屏渲染延迟约 8–12ms(实测于 Chrome 128 + React 19);超过 4 层嵌套的容器,
querySelectorAll平均耗时翻倍。实操建议:
- 用
document.querySelectorAll("*").forEach(el => el.children.length > 5 && console.warn("deep container:", el))扫描可疑节点 - 把表单字段组、卡片列表这类重复结构拆成独立
<template></template>,用content.cloneNode(true)插入,而非硬编码嵌套 - 状态驱动的区块(如加载中/错误态)用
<slot></slot>或<div hidden> 切换,别靠 display: none 堆 div <h3>class 名不是 CSS 钩子,是接口文档</h3> <p>SPA 中 class 名常被 JS 和 CSS 共用,一旦写成 <code>float-left、bg-red-500或tab-active,就等于把样式逻辑、布局逻辑、状态逻辑全焊死在 HTML 里。三个月后想换主题色或改 tab 切换方式,你得 grep 全局、改模板、修 JS、调 CSS——不是维护,是重写。
anything-to-html下载将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
容易踩的坑:
- 用
id="header"绑定 JS 事件,结果同页面多次渲染 Header 组件,getElementById只返回第一个 - 类名含版本号(
card-v3),但没人维护版本映射文档,最后满屏v1/v2/v3并存 - 把 BEM 的
block__element--modifier简化成block-element-modifier,破环解析一致性
实操建议:
- 状态类统一用
--开头:button--loading、list-item--selected,JS 用el.classList.toggle("button--loading")控制 - 组件根元素加
data-component="search-bar",JS 查询走querySelector('[data-component="search-bar"]'),不依赖 class - 禁用所有带样式描述的类名,
red-box改为alert-banner,flex-row改为filter-controls
HTML 不是静态快照,得跟 JS 生命周期对齐
SPA 的 HTML 是活的:路由跳转、组件挂载/卸载、服务端渲染(SSR)水合(hydration)、微前端子应用注入……这些动作若没同步清理 DOM,就会留下残留节点、重复 landmark、错位 tabindex。Lighthouse 的 “logical-tab-order” 失败,90% 源于此。
验证时最容易忽略的点:
- 关闭 CSS 后纯 HTML 是否仍按视觉流顺序可读(黄金测试)
- 键盘 Tab 测试是否从
<nav></nav>→<main></main>→<footer></footer>,而不是跳进某个已卸载组件的旧<div class="modal"> <li>运行 <code>axe.run()后检查是否有 “duplicate-banner”、“multiple-main” 这类严重级错误 - 组件 unmount 时手动移除其插入的所有 landmark 标签(
<main></main>、<aside></aside>等),或用aria-hidden="true"+inert屏蔽 - SSR 水合前,用
document.querySelectorAll('[data-hydrate]').forEach(el => el.removeAttribute('data-hydrate'))清理占位标记 - 微前端场景下,子应用挂载点必须是空
<div id="micro-app-root">,禁止父应用预置语义标签 <p>复杂点不在怎么写新代码,而在老节点是否真被删干净——DOM 不像变量,不会自动 GC。</p> </div>
实操建议:
- 用
- 路由组件 mount 时检查是否已存在










