自定义元素(Web Components)
本教程共 50 篇 · 第 40 篇 · 更新于 2026-08-05 · 约 5 分钟阅读
40. 自定义元素(Web Components)
本节目标:学会把 Svelte 组件编译成 Web Components(自定义元素),掌握 Shadow DOM、属性映射、
$hostrune 和组件选项配置。
什么是自定义元素
Web Components 是浏览器的原生标准,让你创建可复用的自定义 HTML 标签。Svelte 组件可以编译成自定义元素,这样即使不用 Svelte 的项目也能用你的组件。
比如你写了一个 <my-button> 组件,别人在纯 HTML 页面里写 <my-button>点击</my-button> 就能用。
基本用法
在组件里用 <svelte:options> 声明它是一个自定义元素,并指定标签名:
<svelte:options customElement="my-button" />
<script>
let { label = '按钮' } = $props();
</script>
<button>{label}</button>
导入这个组件后,它会自动注册为自定义元素,你可以在任何地方使用:
import './MyButton.svelte';
document.body.innerHTML = '<my-button label="提交"></my-button>';
也可以在 Svelte 模板中当普通标签用:
<my-button label="提交" />
Props 和属性映射
自定义元素的 props 会同时暴露为 JavaScript 属性和 HTML 属性:
const el = document.querySelector('my-button');
// 通过属性读取和设置
console.log(el.label); // '提交'
el.label = '取消'; // 更新组件
// 通过 HTML 属性设置
el.setAttribute('label', '保存');
Note必须显式声明所有 props(如
let { name } = $props()),不能只写let props = $props()。Svelte 需要知道每个 prop 的名字才能把它们暴露到 DOM 元素上。
Shadow DOM
自定义元素默认使用 Shadow DOM 来隔离样式。这意味着组件的 <style> 不会泄漏到外部,外部样式也不会影响组件内部。
可以配置 Shadow DOM 的行为:
<svelte:options
customElement={{
tag: 'my-card',
shadow: 'open' // open / closed / none
}}
/>
| 值 | 说明 |
|---|---|
'open' | Shadow DOM 开放模式,外部 JS 可通过 element.shadowRoot 访问 |
'closed' | Shadow DOM 封闭模式,外部无法访问内部 DOM |
'none' | 不创建 Shadow DOM,样式不隔离,不能用 slot |
Tip开发时用
'open'方便调试,生产环境可用'closed'保护内部结构。也可以根据环境变量动态选择:shadow: import.meta.env.DEV ? 'open' : 'closed'。
Props 高级配置
通过 props 选项可以控制每个 prop 的行为:
<svelte:options
customElement={{
tag: 'my-input',
props: {
value: {
attribute: 'data-value', // 自定义 HTML 属性名
reflect: true, // 值变化时同步回 HTML 属性
type: 'String' // 类型转换
},
count: {
type: 'Number' // 属性值转成数字
}
}
}}
/>
<script>
let { value = '', count = 0 } = $props();
</script>
<input bind:value />
<p>计数:{count}</p>
type 支持的值:'String'、'Boolean'、'Number'、'Array'、'Object'。HTML 属性是字符串,type 决定了属性值和 prop 值之间的转换方式。
$host rune
$host() 返回当前自定义元素的 DOM 节点。当你需要在组件内部访问宿主元素时用它:
<svelte:options customElement="my-tooltip" />
<script>
let { position = 'top' } = $props();
function handleClick() {
// $host() 返回 <my-tooltip> 元素
const el = $host();
el.dispatchEvent(new CustomEvent('activated', {
detail: { position }
}));
}
</script>
<button onclick={handleClick}>激活</button>
Note
$host()只能在编译为自定义元素的组件中使用。在普通组件中调用$host()会导致编译错误。
事件分发
自定义元素通过 CustomEvent 和 dispatchEvent 来发送事件。外部用 addEventListener 监听:
const el = document.querySelector('my-tooltip');
el.addEventListener('activated', (e) => {
console.log(e.detail.position); // 'top'
});
Warning不要用
on开头的 prop 名(如onclick、onselect)。浏览器会把on开头的属性当作事件监听器。比如oneworld会被解析成监听eworld事件。
extend 选项
extend 让你自定义元素类,实现高级功能(如表单关联):
<svelte:options
customElement={{
tag: 'my-form-input',
extend: (BaseClass) => {
return class extends BaseClass {
static formAssociated = true;
constructor() {
super();
this.internals = this.attachInternals();
}
// 在组件挂载前就可调用的方法
setValue(value) {
this.internals.setFormValue(value);
}
};
}
}}
/>
<script>
let { attachedInternals } = $props();
function check() {
attachedInternals.checkValidity();
}
</script>
<input type="text" />
生命周期
自定义元素的生命周期和 Svelte 组件略有不同:
- 创建:元素被
document.createElement或 HTML 解析创建时,内部 Svelte 组件不会立即创建 - 挂载:元素插入 DOM 后的下一个 tick,内部组件才创建
- 卸载:元素从 DOM 移除后的下一个 tick,内部组件才销毁
这意味着在元素插入 DOM 前设置的 props 会被暂存,等组件创建后再应用。
注意事项
用 Svelte 写自定义元素时要注意:
- 样式是封装的(Shadow DOM),全局 CSS 选择器无法直接影响组件内部,但 CSS 自定义属性(CSS variables)可以穿透 Shadow DOM 边界
- 样式以 JS 字符串内联到组件中,而不是提取成单独 CSS 文件
- 自定义元素不适合 SSR,因为 Shadow DOM 在 JS 加载前不可见
- slot 内容是即时渲染的,不受组件内
{#if}条件控制 - Context 不能跨自定义元素传递,只能在同一个自定义元素内部的 Svelte 组件间使用
本节回顾
<svelte:options customElement="tag-name" />把组件编译为 Web Component- Props 自动暴露为 DOM 属性和 HTML 属性,必须显式声明
props选项控制属性名映射、值反射和类型转换- Shadow DOM 默认开启,配置
'open'/'closed'/'none' $host()访问宿主元素,用于分发自定义事件extend选项可扩展元素类,实现表单关联等高级功能- 注意样式封装、slot 行为和 SSR 限制等差异