跳至主要内容

编译器和 API

自定义元素 API

在 GitHub 上编辑此页面

Svelte 组件还可以使用 customElement: true 编译器选项编译为自定义元素(又称 Web 组件)。你应该使用 <svelte:options> 元素为组件指定一个标记名称。

<svelte:options customElement="my-element" />

<!-- in Svelte 3, do this instead:
<svelte:options tag="my-element" />
-->

<script>
	export let name = 'world';
</script>

<h1>Hello {name}!</h1>
<slot />

你可以省略任何你不希望公开的内部组件的标记名称,并像常规 Svelte 组件一样使用它们。组件的使用者仍然可以在需要时使用包含自定义元素构造函数的静态 element 属性对其进行命名,当 customElement 编译器选项为 true 时,该属性可用。

ts
import MyElement from './MyElement.svelte';
customElements.define('my-element', MyElement.element);
// In Svelte 3, do this instead:
// customElements.define('my-element', MyElement);

定义自定义元素后,可以将其用作常规 DOM 元素

ts
document.body.innerHTML = `
<my-element>
<p>This is some slotted content</p>
</my-element>
`;

默认情况下,自定义元素使用 accessors: true 编译,这意味着任何 props 都将作为 DOM 元素的属性公开(以及在可能的情况下作为属性可读/可写)。

要防止这种情况,请将 accessors={false} 添加到 <svelte:options>

ts
const el = document.querySelector('my-element');
// get the current value of the 'name' prop
console.log(el.name);
// set a new value, updating the shadow DOM
el.name = 'everybody';

组件生命周期

自定义元素是使用包装器方法从 Svelte 组件创建的。这意味着内部 Svelte 组件不知道它是一个自定义元素。自定义元素包装器负责适当地处理其生命周期。

当创建自定义元素时,它包装的 Svelte 组件不会立即创建。它仅在调用 connectedCallback 之后的下一个滴答中创建。在将自定义元素插入 DOM 之前分配给它的属性将被暂时保存,然后在组件创建时设置,这样它们的价值就不会丢失。但这对在自定义元素上调用导出的函数不起作用,它们仅在元素挂载后可用。如果你需要在组件创建之前调用函数,你可以使用 extend 选项 来解决此问题。

当使用 Svelte 编写的自定义元素被创建或更新时,影子 DOM 将在下一个滴答中反映值,而不是立即反映。通过这种方式可以对更新进行批处理,并且暂时(但同步)将元素从 DOM 中分离的 DOM 移动不会导致卸载内部组件。

在调用 disconnectedCallback 之后的下一个滴答中销毁内部 Svelte 组件。

组件选项

在构建自定义元素时,你可以通过在 Svelte 4 中将 customElement 定义为 <svelte:options> 中的对象来定制多个方面。此对象可能包含以下属性

  • tag:自定义元素名称的强制 tag 属性
  • shadow:一个可选属性,可以将其设置为 "none" 以放弃影子根创建。请注意,样式不再封装,并且你不能使用插槽
  • 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) => {
			// Extend the class so we can let it participate in HTML forms
			return class extends customElementConstructor {
				static formAssociated = true;

				constructor() {
					super();
					this.attachedInternals = this.attachInternals();
				}

				// Add the function here, not below in the component so that
				// it's always available, not just when the inner Svelte component
				// is mounted
				randomIndex() {
					this.elementIndex = Math.random();
				}
			};
		}
	}}
/>

<script>
	export let elementIndex;
	export let attachedInternals;
	// ...
	function check() {
		attachedInternals.checkValidity();
	}
</script>

...

注意事项和限制

自定义元素可能是打包组件以供在非 Svelte 应用程序中使用的一种有用方式,因为它们将与原生 HTML 和 JavaScript 以及 大多数框架 一起工作。但是,有一些重要的差异需要注意

  • 样式是封装的,而不是仅仅限定范围的(除非您设置 shadow: "none")。这意味着任何非组件样式(例如您可能在 global.css 文件中拥有的样式)都不会应用于自定义元素,包括带有 :global(...) 修饰符的样式
  • 样式不会作为单独的 .css 文件提取出来,而是作为 JavaScript 字符串内联到组件中
  • 自定义元素通常不适合服务器端渲染,因为在 JavaScript 加载之前,影子 DOM 是不可见的
  • 在 Svelte 中,插槽内容以延迟方式渲染。在 DOM 中,它以急切方式渲染。换句话说,即使组件的 <slot> 元素位于 {#if ...} 块内,它也将始终被创建。类似地,在 {#each ...} 块中包含 <slot> 不会导致插槽内容被多次渲染
  • let: 指令不起作用,因为自定义元素没有办法将数据传递给填充插槽的父组件
  • 需要多态填充来支持较旧的浏览器
  • 您可以在自定义元素内的常规 Svelte 组件之间使用 Svelte 的上下文功能,但不能跨自定义元素使用它们。换句话说,您不能在父自定义元素上使用 setContext,并在子自定义元素中使用 getContext 读取它。