Web 组件包括自定义元素、Shadow DOM 和 HTML 模板:这些平台基元用于创建可重用、与框架无关的组件,其标记和样式与文档封装在一起。可用于设计体系、可嵌入微件和独立界面。
以下规则适用于所有类别。检索与当前任务相关的特定指南;每份指南都会重申其所需的上下文,并记录其特有的 API 和陷阱。
确定是否需要自定义元素
仅当您需要对标记使用封装的行为、生命周期或可重用的 JS 支持的 API 时,才应使用自定义元素。在以下情况下,请勿构建:
- 使用 CSS 的普通 HTML 元素即可。与重新实现相比,设置了样式的
<button>或<details>更易于访问,且成本更低。 - 该内容纯粹是展示性的,没有任何行为。原生元素上的类性能更高,并且可以使用
@layer或@scope限定范围。 - 您只需要在服务器上设置样式范围,而无需交互;一个限定范围的样式表或声明式 Shadow DOM(无需升级)可能就足够了。
重建原生语义(重新创建焦点、激活和 ARIA 的自定义 <my-button>)是最常见的误用:只有在重复使用原生元素时,您才能免费继承平台的无障碍功能。如果有必要使用 <my-button> 样式的封装容器(通常用于共享样式钩子、额外标记或原生元素无法表达的行为),请封装真实的 <button>,而不是重新实现它(请参阅设置 Web 组件的样式,了解如何将样式转发到内部原生元素)。
测试的目的是确定您是否需要封装状态、可编写脚本的 API 或原生元素无法表达的行为,而不仅仅是不同的外观。点赞/收藏切换开关符合条件:它带有锁存的网域状态,将该状态作为周围网页的样式钩子公开,并分派自己的 change 事件,而这些都是样式化 <button> 无法提供的。如果按钮仅在按下时改变颜色,则不符合要求:请使用 <button> + CSS。在构建时,请根据需要使用自定义状态或属性对状态进行建模(请参阅自定义元素)。
将工具与代码库匹配
某个元素是否应为自定义元素取决于项目已使用的元素。智能体往往会过度抽象,并过于急切地寻求新元素。如有疑问,通常应选择更轻量的选项(原生元素、CSS、服务器模板),如果后续确实需要重复使用或实现特定行为,再将其升级为自定义元素。启发法(大致优先级):
- 代码库已有一个组件模型(React、Vue、Svelte 等)。默认使用该框架的组件来构建应用内界面。仅当组件必须脱离或超越框架时,才应使用自定义元素:跨应用或框架共享的设计系统基元、嵌入到框架无法控制的标记中的 widget,或必须作为纯 HTML 运行的输出(电子邮件、CMS、SSR 片段)。请勿为普通应用内视图建立并行组件系统。
- 没有工具,大多是静态标记。首选纯 HTML + CSS 或服务器模板/部分。只有在您确实需要标记上的行为、生命周期或 JS 支持的 API 时,自定义元素才有用武之地,而不仅仅是为了共享标记,因为服务器包含或
<template>可以在不提供任何 JS 的情况下实现这一点。 - 具有互动岛的静态网站生成器。静态呈现标记;为互动部分保留自定义元素。对于“大量标记,少量交互”的情况,请将标记保留在 Light DOM(或声明式 Shadow DOM)中,并让一个小型自定义元素逐步增强它,而不是从 JS 重新渲染所有内容。
- 您可以进一步扩展内置元素。通常,最好尽可能使用原生元素(CSS、一些监听器、偶尔使用的伪元素),而不是自定义元素,因为这样可以免费保留平台的无障碍功能和行为。当权宜解决方法不再可维护时,即当您需要与元素的内置行为作斗争,或者编写体验变得比干净的组件 API 更糟糕时,请改用自定义元素。内置元素需要一点 JS 本身并不是替换它的理由。
跨领域规则
以下所有指南均适用这些原则:
- 强制性:标记名称必须采用烤串命名法,且至少包含一个连字符(例如
my-element)。这是自定义元素与当前和未来内置元素的区别所在。 - 强制性:在构造函数中,先调用
super(),然后再访问this;在任何读取或写入this的语句之后调用它都会抛出异常。 - 强制要求:在 shadow 模板中使用语义元素(
<button>、<nav>、<ul>)。影子树中的大多数无障碍功能故障都源于非语义标记;槽不会更改其投影内容的角色。 - 强制性:ARIA
id引用(aria-labelledby、aria-describedby、aria-controls)不能跨越 shadow 边界;引用元素及其指向的id必须位于同一树中。 - 首选
attachShadow({ mode: 'open' }),以便开发者工具、网页脚本和服务器水合可以访问element.shadowRoot。仅在必须对页面脚本隐藏内部结构时,才使用'closed'。 - 避免使用自定义内置元素(
extends HTMLButtonElement带有is="…");Safari 已永久拒绝支持这些元素。
应用场景指南矩阵
确定任务并检索其指南:
| 如果您需要… | 检索 |
|---|---|
| 定义具有行为和生命周期的新语义标记 | 自定义元素 |
| 将样式和标记限定在组件范围内,并将使用者内容投影到组件中 | Shadow DOM、模板和槽 |
| 在服务器上渲染组件的 shadow tree (SSR),而不会出现未设置样式的内容闪烁 | 预渲染自定义元素 |
跨越阴影边界的样式(主题、::part、自定义属性) |
设置 Web 组件的样式 |
将自定义控件的值提交到周围的 <form> |
与表单关联的自定义元素 |
| 让自定义元素更易于使用(角色、焦点、跨 Shadow Root 的 ARIA) | 无障碍 Web 组件 |