跳至主要内容

自定义元素

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 元素一样使用它。

var document: Documentdocument.Document.body: HTMLElement

Specifies the beginning and end of the document body.

MDN Reference

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

任何 属性都作为 DOM 元素的属性公开(以及在可能的情况下作为属性可读/可写)。

const const el: Element | nullel = 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 calling require('console').

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
(const el: Element | nullel.name);
// set a new value, updating the shadow DOM const el: Element | nullel.name = 'everybody';

请注意,您需要显式列出所有属性,即,在没有在 组件选项中声明props的情况下执行let props = $props()意味着 Svelte 无法知道要将哪些属性作为 DOM 元素上的属性公开。

组件生命周期

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

创建自定义元素时,它包装的 Svelte 组件不会立即创建。它仅在调用connectedCallback后的下一个 tick 中创建。在将其插入 DOM 之前分配给自定义元素的属性会暂时保存,然后在组件创建时设置,因此它们的值不会丢失。但是,对自定义元素调用导出的函数的方式却不同,它们只有在元素挂载后才可用。如果您需要在组件创建之前调用函数,可以通过使用 extend 选项来解决此问题。

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

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

组件选项

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

  • tag: string:自定义元素名称的可选tag属性。如果设置,则在导入此组件时,将使用文档的customElements注册表定义具有此标签名称的自定义元素。
  • shadow:一个可选属性,可以设置为"none"以放弃影子根创建。请注意,样式随后不再封装,并且您不能使用插槽。
  • props:一个可选属性,用于修改组件属性的某些细节和行为。它提供了以下设置
    • attribute: string:要更新自定义元素的属性,您有两个选择:如上所述,将属性设置为自定义元素的引用,或者使用 HTML 属性。对于后者,默认属性名称是小写属性名称。通过分配attribute: "<desired name>"来修改此属性。
    • reflect: boolean:默认情况下,更新的属性值不会反映回 DOM。要启用此行为,请设置reflect: true
    • type: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object':在将属性值转换为属性值并将其反映回来时,默认情况下,属性值被假定为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>

...

注意事项和限制

自定义元素可以成为一种有用的方式,用于打包组件以便在非 Svelte 应用程序中使用,因为它们可以与普通 HTML 和 JavaScript 以及 大多数框架一起使用。但是,有一些重要的区别需要注意。

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

在 GitHub 上编辑此页面

上一页 下一页