自定义元素名必须含破折号,注册须在dom渲染前完成,命名需统一前缀避免冲突;class/id用bem规范隔离;data-*属性须带业务上下文;事件和全局变量需加组件标识;css与js隔离边界须对齐。

自定义元素名必须带破折号,否则注册直接失败
浏览器强制要求 customElements.define() 的第一个参数必须含至少一个破折号(-),否则抛出 DOMException: The element name is not valid。这不是兼容性问题,是 HTML 规范层的硬约束。
- 合法:
my-button、shop-product-card、abc-modal - 非法:
mybutton、MyButton、my_button、tabs - 注册时机很关键:必须在标签首次出现在 DOM 前完成注册;若已渲染再注册,需手动调用
customElements.upgrade() - 前缀(如
shop-、abc-)不是语法要求,但它是多团队协作中避免命名冲突的最低成本手段——没人能强制你不用button,但你可以约定所有内部组件都以yourteam-开头
class/id 命名必须绑定组件上下文,不能泛化
HTML 没有原生命名空间,class="btn" 或 id="modal" 这类泛化名在多个团队组件共存时必然撞车。BEM 是目前最轻量、可落地的隔离方案。
- 根元素用唯一 block 名:
class="user-profile",不是class="profile" - 子元素严格双下划线:
class="user-profile__avatar",不写成user-profile-avatar(易与其它连字符命名混淆) - 状态修饰符用双短横:
class="user-profile--loading",避免is-loading这类无归属的状态名 - 禁止用样式描述命名:
class="big-red-text"会随主题变更失效;class="user-profile__heading"才可持续
data-* 属性必须携带业务上下文,裸名等于没写
data-id="123" 看似简洁,实则埋雷——三个月后没人知道这是用户 ID、商品 SKU 还是订单流水号。它本质是前端与后端/业务逻辑的契约接口,必须显式声明来源和语义。
- 安全写法:
data-user-id="123"、data-api-source="user/profile"、data-user-status="active" - 配合
stylelint的selector-attribute-pattern规则,拒绝不含业务前缀的data-* - 不要把多个字段塞进单个 key(如
data-state),更新一个字段就得重写整个对象,增加竞态风险 - JS 中读取时也应保持一致性:
el.dataset.userId对应data-user-id,大小写自动转驼峰,但前提是命名本身有意义
事件与全局变量必须加组件标识,否则监听器互相污染
组件发 new CustomEvent("submit"),其他任意组件监听同名事件就会误收;挂 window.initWidget 也可能被覆盖。这不是“理论上可能”,而是上线后必现的问题。
- 事件类型加冒号分隔的组件标识:
"user-profile:submit"、"user-profile:load-error",比userProfileSubmit更易区分层级 - 全局变量全包进命名空间对象:
window.UserProfile = { init(), destroy(), config: {} },而不是零散挂window.init、window.userConfig - 第三方脚本片段用 IIFE 兜底:
(function() { /* 私有变量和函数 */ })();,防止变量泄漏 - CSS 和 JS 的隔离边界必须对齐:只改
class名但 JS 还用document.getElementById("modal"),或 CSS 写了.user-profile__header但 HTML 漏了 class,都会导致逻辑断裂
真实项目里最常被忽略的,不是“要不要加前缀”,而是“前缀加在哪一层”。shop-product-card 是自定义标签名,shop-product-card__image 是 class,data-shop-product-id 是属性——这三层前缀必须一致且可追溯,否则隔离就形同虚设。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











