Skip to main content

Custom elements

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

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

<script>
	let { name = 'world' } = $props();
</script>

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

您可以为不希望暴露的内部组件省略标签名,并像常规 Svelte 组件一样使用它们。组件的消费者如果需要,之后仍然可以为其命名,使用包含自定义元素构造函数的静态element属性,该属性在customElement编译选项为true时可用。

import 
type MyElement = SvelteComponent<Record<string, any>, any, any>
const MyElement: LegacyComponentType
MyElement
from './MyElement.svelte';
var customElements: CustomElementRegistry

Defines a new custom element, mapping the given name to the given constructor as an autonomous custom element.

MDN Reference

customElements
.CustomElementRegistry.define(name: string, constructor: CustomElementConstructor, options?: ElementDefinitionOptions): voiddefine('my-element', const MyElement: LegacyComponentTypeMyElement.element);

一旦定义了自定义元素,就可以将其用作常规 DOM 元素:

module document
var document: Document
document
.Document.body: HTMLElement

Specifies the beginning and end of the document body.

MDN Reference

body
.Element.innerHTML: stringinnerHTML = `
<my-element> <p>This is some slotted content</p> </my-element> `;

任何props都作为 DOM 元素的属性暴露(同时在可能的情况下,作为可读/可写的属性)。

const 
module el
const el: Element | null
el
= var document: Documentdocument.ParentNode.querySelector<Element>(selectors: string): Element | null (+4 overloads)

Returns the first element that is a descendant of node that matches selectors.

MDN Reference

querySelector
('my-element');
// get the current value of the 'name' prop var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object’s methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
//   Error: Whoops, something bad happened
//     at [eval]:5:15
//     at Script.runInThisContext (node:vm:132:18)
//     at Object.runInThisContext (node:vm:309:38)
//     at node:internal/process/execution:77:19
//     at [eval]-wrapper:6:22
//     at evalScript (node:internal/process/execution:76:60)
//     at node:internal/main/eval_string:23:3

const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);

myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err

const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
@seesource
console
.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100
log
(
module el
const el: Element | null
el
.name);
// set a new value, updating the shadow DOM
module el
const el: Element | null
el
.name = 'everybody';

请注意,您需要明确列出所有属性,即在不声明 props组件选项 的情况下,执行 let props = $props(),这意味着 Svelte 无法知道哪些属性应暴露为 DOM 元素的属性。

组件生命周期

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

当创建自定义元素时,它包裹的 Svelte 组件不会立即创建。它只会在调用 connectedCallback 后的下一个 tick 中创建。在将自定义元素插入 DOM 之前分配给它的属性会被临时保存,然后在组件创建时设置,因此它们的值不会丢失。然而,对于在自定义元素上调用导出的函数,这种情况并不适用,它们只有在元素挂载后才能使用。如果您需要在组件创建之前调用函数,可以通过使用 extend 选项 来解决这个问题。

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

内部 Svelte 组件在调用disconnectedCallback后在下个 tick 中被销毁。

组件选项

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

  • 标签:字符串:为自定义元素名称的可选 标签 属性。如果设置,则在导入此组件时,将使用文档的 customElements 注册表定义具有此标签名的自定义元素。
  • shadow:一个可选属性,可以设置为"none"以跳过阴影根的创建。请注意,此时样式将不再封装,并且无法使用插槽
  • props:一个可选属性,用于修改组件属性的某些细节和行为。它提供以下设置:
    • 属性:字符串:要更新自定义元素的属性,你有两种选择:要么像上面所示,在自定义元素的引用上设置属性,或者使用 HTML 属性。对于后者,默认属性名是属性名的小写形式。通过将属性:"< 期望的名称>"赋值来修改它。
    • 反射:布尔型:默认情况下,更新的属性值不会反映回 DOM。要启用此行为,设置反射: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>
	let { elementIndex, attachedInternals } = $props();
	// ...
	function check() {
		attachedInternals.checkValidity();
	}
</script>

...

[!注意] 虽然 TypeScript 在 extend 函数中受支持,但它受到限制:您需要在其中一个脚本上设置 lang="ts",并且只能在其中使用 可擦除语法。它们不会被脚本预处理器处理。

注意事项和限制

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

  • 样式是封装的,而不是仅仅作用域的(除非您设置了shadow: "none")。这意味着任何非组件样式(例如您可能在global.css文件中拥有的样式)都不会应用于自定义元素,包括带有:global(...)修饰符的样式。
  • 而不是作为单独的.css 文件提取,样式以 JavaScript 字符串的形式内联到组件中
  • 自定义元素通常不适用于服务器端渲染,因为阴影 DOM 在 JavaScript 加载之前是不可见的
  • 在 Svelte 中,插槽内容会懒加载地渲染*。在 DOM 中,它会急切地渲染*。换句话说,即使组件的<slot>元素位于一个{#if ...}块中,它也总会被创建。同样,在一个{#each ...}块中包含一个<slot>也不会导致插槽内容被多次渲染。**
  • 过时的let:指令没有效果,因为自定义元素没有将数据传递给填充插槽的父组件的方法
  • Polyfills 是支持旧浏览器的必需品
  • 您可以在自定义元素中在常规 Svelte 组件之间使用 Svelte 的上下文功能,但不能在自定义元素之间使用。换句话说,您不能在父自定义元素上使用setContext,然后在子自定义元素中使用getContext来读取。
  • 不要声明以on开头的属性或属性,因为它们的用法将被解释为事件监听器。换句话说,Svelte 将 <custom-element oneworld={true}></custom-element> 视为 customElement.addEventListener('eworld', true) (而不是customElement.oneworld = true)。

Edit this page on GitHub llms.txt