html可通过data-属性驱动行为、slot实现内容插槽、is复用内置标签、attributechangedcallback监听变更,实现类组件的可插拔能力。

HTML 本身没有“组件”概念,但通过合理使用属性(尤其是 data-、slot、is 和自定义元素的 attribute),可以实现接近“可插拔”的行为控制和内容注入——关键不在标签名,而在属性如何触发逻辑、分发内容、隔离上下文。
用 data- 属性驱动行为,避免硬编码逻辑
当你想让一个通用容器(比如 <div class="card">)在不同页面表现不同(加载数据、切换主题、启用编辑),别写一堆 <code>if (page === 'admin') {...},而是把决策权交给属性。
-
data-mode="edit"→ 触发表单渲染,而非只读展示 -
data-source="/api/users/123"→ JS 自动 fetch 并填充,不写死 URL -
data-theme="dark"→ 切换 CSS 变量或 class,不改结构
注意:dataset 会自动将 data-user-id 转成 userId(驼峰),但值始终是字符串;需要数字时得手动 parseInt()。别依赖属性值类型,它永远是字符串。
用 slot 实现内容插槽,而不是拼接 innerHTML
如果你封装了一个 <modal></modal> 自定义元素,用户传内容的方式决定了它是否真正“可插拔”。直接要求传 HTML 字符串(content: '<h2>标题</h2>')会导致 XSS 风险、无事件绑定、无法响应式更新。
- 正确做法:用
<slot></slot>让用户写真实 HTML 标签,由 Shadow DOM 自动投影 - 支持具名插槽:
<slot name="header"></slot>+<h2 slot="header">标题</h2> - 默认插槽没
name,但必须有且仅有一个;多个默认插槽会被合并,顺序按 DOM 流
警告:如果自定义元素没调用 this.attachShadow({ mode: 'open' }),<slot></slot> 不生效,浏览器会把它当普通文本显示。
用 is 属性复用基础标签,绕过 Custom Elements 的注册限制
某些场景下你不能或不想全局定义 customElements.define('my-button', ...)(比如 CMS 输出的静态 HTML、第三方脚本未就绪)。这时可以用 is 属性让原生标签“假装”是自定义元素:
<button is="my-button" data-variant="primary">提交</button>
前提是:你已提前注册了 my-button 类,并继承自 HTMLButtonElement(不是 HTMLElement):
class MyButton extends HTMLButtonElement {
connectedCallback() {
this.classList.add(`btn-${this.getAttribute('data-variant')}`);
}
}
customElements.define('my-button', MyButton, { extends: 'button' });
这个技巧只对可扩展的内置标签有效(button、input、article 等),且必须用 { extends: 'xxx' } 注册。漏掉 extends,is 属性完全无效。
属性变更监听要主动,attributeChangedCallback 不是万能的
在 Custom Element 中,想响应 data-loading="true" 变化?光写 observedAttributes 不够,你还得手动实现 attributeChangedCallback:
- 必须显式返回
['data-loading', 'data-error']数组,名字必须完全匹配(包括大小写) - 回调里拿到的是旧值和新值,但新值仍是字符串,
"false"不等于false - 首次初始化时也会触发一次,此时 oldVal 是
null,别直接===比较
最易忽略的一点:如果属性是通过 JS 设置的(el.setAttribute('data-loading', true)),浏览器会自动转成字符串 "true";但如果是 HTML 写死的 data-loading(无值),它的值是空字符串 "",不是 "true" —— 这个差异会让布尔判断出错。











