Svelte 组件也可以使用 customElement: true
编译器选项编译为自定义元素(也称为 Web 组件)。您应该使用 <svelte:options>
元素 为组件指定一个标签名称。
<svelte:options customElement="my-element" />
<!-- 在 Svelte 3 中,这样做:
<svelte:options tag="my-element" />
-->
<script>
export let name = 'world';
</script>
<h1>Hello {name}!</h1>
<slot />
您可以省略您不想公开的任何内部组件的标签名称,并像使用常规 Svelte 组件一样使用它们。如果需要,组件的用户之后可以使用静态 element
属性对其进行命名,该属性包含自定义元素构造函数,并且在 customElement
编译器选项为 true
时可用。
import MyElement from './MyElement.svelte';
customElements.define('my-element', MyElement.element);
// 在 Svelte 3 中,这样做:
// customElements.define('my-element', MyElement);
一旦自定义元素被定义,它就可以像常规 DOM 元素一样使用:
document.body.innerHTML = `
<my-element>
<p>This is some slotted content</p>
</my-element>
`;
默认情况下,自定义元素使用 accessors: true
编译,这意味着任何 props 都作为 DOM 元素的属性公开(并且在可能的情况下,作为属性可读/可写)。
要阻止这种行为,在 <svelte:options>
中添加 accessors={false}
。
const el = document.querySelector('my-element');
// 获取 'name' prop 的当前值
console.log(el.name);
// 设置一个新值,更新 shadow DOM
el.name = 'everybody';
组件生命周期
使用 Svelte 编写的自定义元素使用包装器方法创建。这意味着内部的 Svelte 组件并不知道它是一个自定义元素。自定义元素包装器负责适当地处理其生命周期。
当创建自定义元素时,它包装的 Svelte 组件不会立即创建。它仅在调用 connectedCallback
后的下一个滴答中创建。在将其插入 DOM 之前分配给自定义元素的属性将被暂时保存,然后在组件创建时设置,这样它们的值就不会丢失。但是,对于调用自定义元素上导出的函数则不适用,它们只有在元素挂载后才可用。如果您需要在组件创建之前调用函数,可以使用 extend
选项 来解决。
当使用 Svelte 编写的自定义元素被创建或更新时,shadow DOM 将在下一个滴答中反映该值,而不是立即。这样更新可以批量处理,并且 DOM 的移动(即使暂时(但同步)地将元素从 DOM 中分离)不会导致内部组件卸载。
内部 Svelte 组件在调用 disconnectedCallback
后的下一个滴答中被销毁。
组件选项
在构建自定义元素时,您可以通过在 Svelte 4 中的 <svelte:options>
内定义 customElement
为对象来定制几个方面。此对象可能包含以下属性:
tag
: 自定义元素名称的强制性tag
属性shadow
: 一个可选属性,可以设置为"none"
以放弃创建 shadow root。注意,样式将不再封装,并且您不能使用插槽props
: 一个可选属性,用于修改组件属性的某些细节和行为。它提供以下设置:attribute: string
: 更新自定义元素的 prop,您有两种选择:要么像上面所示的那样在自定义元素的引用上设置属性,要么使用 HTML 属性。默认情况下,属性名称是小写属性名称。通过分配attribute: "<desired name>"
来修改它。reflect: boolean
: 默认情况下,更新的 prop 值不会反射回 DOM。要启用此行为,请设置reflect: true
。type: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object'
: 在将属性值转换为 prop 值并反射回去时,默认假定 prop 值是String
。这可能不总是准确的。例如,对于数字类型,使用type: "Number"
定义。您不需要列出所有属性,未列出的将使用默认设置。
extend
: 一个可选属性,期望一个函数作为其参数。它传递由 Svelte 生成的自定义元素类,并期望您返回一个自定义元素类。如果您对自定义元素的生命周期有非常特定的要求,或者想要增强类以使用 ElementInternals 以获得更好的 HTML 表单集成,这将非常有用。
<svelte:options
customElement={{
tag: 'custom-element',
shadow: 'none',
props: {
name: { reflect: true, type: 'Number', attribute: 'element-index' }
},
extend: (customElementConstructor) => {
// 扩展类,以便我们可以让它参与 HTML 表单
return class extends customElementConstructor {
static formAssociated = true;
constructor() {
super();
this.attachedInternals = this.attachInternals();
}
// 在这里添加函数,而不是在组件中添加,以便它总是可用的,而不仅仅是在内部 Svelte 组件挂载时
randomIndex() {
this.elementIndex = Math.random();
}
};
}
}}
/>
注意事项和限制
自定义元素可以是将组件打包用于非 Svelte 应用程序的有用方式,因为它们将与纯 HTML 和 JavaScript 以及 大多数框架 一起工作。然而,有一些重要的差异需要注意:
- 样式是 encapsulated(封装的),而不仅仅是 scoped(作用域化的)(除非您设置
shadow: "none"
)。这意味着任何非组件样式(例如您可能在global.css
文件中拥有的样式)将不适用于自定义元素,包括带有:global(...)
修饰符的样式 - 样式不会被提取为单独的 .css 文件,而是作为 JavaScript 字符串内联到组件中
- 自定义元素通常不适合用于服务器端渲染,因为 shadow DOM 在 JavaScript 加载之前是不可见的
- 在 Svelte 中,插槽内容渲染是 lazily(惰性地)。在 DOM 中,它是 eagerly(急切地)渲染的。换句话说,即使组件的
<slot>
元素在{#if ...}
块内,它也会被创建。同样,将<slot>
包括在{#each ...}
块中也不会导致插槽内容被多次渲染 let:
指令没有效果,因为自定义元素没有将数据传递给填充插槽的父组件的方式- 需要 polyfills 来支持旧版浏览器
- 您可以在自定义元素内的常规 Svelte 组件之间使用 Svelte 的上下文功能,但不能跨自定义元素使用它们。换句话说,您不能在父自定义元素上使用
setContext
并在子自定义元素中使用getContext
读取它。