Skip to main content

编译器和 API

自定义元素 API

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

Svelte components can also be compiled to custom elements (aka web components) using the customElement: true compiler option. You should specify a tag name for the component using the <svelte:options> element.

<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 时可用。

You can leave out the tag name for any of your inner components which you don't want to expose and use them like regular Svelte components. Consumers of the component can still name it afterwards if needed, using the static element property which contains the custom element constructor and which is available when the customElement compiler option is 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 元素:

Once a custom element has been defined, it can be used as a regular DOM element:

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

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

By default, custom elements are compiled with accessors: true, which means that any props are exposed as properties of the DOM element (as well as being readable/writable as attributes, where possible).

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

To prevent this, add accessors={false} to <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';

组件生命周期(Component lifecycle)

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

Custom elements are created from Svelte components using a wrapper approach. This means the inner Svelte component has no knowledge that it is a custom element. The custom element wrapper takes care of handling its lifecycle appropriately.

创建自定义元素时,不会立即创建它所封装的 Svelte 组件。 它仅在调用 connectedCallback 后的下一个时钟周期内创建。 在插入 DOM 之前分配给自定义元素的属性会暂时保存,然后在创建组件时设置,因此它们的值不会丢失。 不过,这对于在自定义元素上调用导出函数不起作用,它们仅在元素安装后才可用。 如果你需要在创建组件之前调用函数,可以使用 extend 选项

When a custom element is created, the Svelte component it wraps is not created right away. It is only created in the next tick after the connectedCallback is invoked. Properties assigned to the custom element before it is inserted into the DOM are temporarily saved and then set on component creation, so their values are not lost. The same does not work for invoking exported functions on the custom element though, they are only available after the element has mounted. If you need to invoke functions before component creation, you can work around it by using the extend option.

当创建或更新使用 Svelte 编写的自定义元素时,shadow DOM 将在下一个刻度中反映该值,而不是立即反映。 这样可以批量更新,并且临时(但同步)从 DOM 分离元素的 DOM 移动不会导致卸载内部组件。

When a custom element written with Svelte is created or updated, the shadow DOM will reflect the value in the next tick, not immediately. This way updates can be batched, and DOM moves which temporarily (but synchronously) detach the element from the DOM don't lead to unmounting the inner component.

调用 disconnectedCallback 后,内部 Svelte 组件将在下一个时钟周期内被销毁。

The inner Svelte component is destroyed in the next tick after the disconnectedCallback is invoked.

组件选项(Component options)

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

When constructing a custom element, you can tailor several aspects by defining customElement as an object within <svelte:options> since Svelte 4. This object may contain the following properties:

  • 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>

...

注意事项和限制(Caveats and limitations)

自定义元素是打包组件以供非 Svelte 应用使用的有用方法,因为它们可以与普通 HTML 和 JavaScript 以及 大多数框架 一起使用。 然而,有一些重要的差异需要注意:

Custom elements can be a useful way to package components for consumption in a non-Svelte app, as they will work with vanilla HTML and JavaScript as well as most frameworks. There are, however, some important differences to be aware of:

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