Skip to main content

自定义元素

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" />

<script>
	let { name = 'world' } = $props();
</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.

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 元素:

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

module document
var document: Document
document
.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> `;

另一种方法是设置代理以绕过 CORS 问题。

¥Any props are exposed as properties of the DOM element (as well as being readable/writable as attributes, where possible).

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 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
(
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';

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

¥Note that you need to list out all properties explicitly, i.e. doing let props = $props() without declaring props in the component options means that Svelte can’t know which props to expose as properties on the DOM element.

组件生命周期(Component lifecycle)

¥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)

¥Component options

构建自定义元素时,可以通过将 customElement 定义为 Svelte 4 以来的 <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: string:自定义元素名称的可选 tag 属性。如果设置,则在导入此组件时,将使用文档的 customElements 注册表定义具有此标签名称的自定义元素。

    ¥tag: string: an optional tag property for the custom element’s name. If set, a custom element with this tag name will be defined with the document’s customElements registry upon importing this component.

  • shadow:可选属性,可设置为 "none" 以放弃影子根创建。请注意,样式不再被封装,并且你不能使用插槽

    ¥shadow: an optional property that can be set to "none" to forgo shadow root creation. Note that styles are then no longer encapsulated, and you can’t use slots

  • props:一个可选属性,用于修改组件属性的某些细节和行为。它提供以下设置:

    ¥props: an optional property to modify certain details and behaviors of your component’s properties. It offers the following settings:

    • attribute: string:要更新自定义元素的 prop,你有两种选择:如上所示在自定义元素的引用上设置属性或使用 HTML 属性。对于后者,默认属性名称是小写属性名称。通过分配 attribute: "<desired name>" 进行修改。

      ¥attribute: string: To update a custom element’s prop, you have two alternatives: either set the property on the custom element’s reference as illustrated above or use an HTML attribute. For the latter, the default attribute name is the lowercase property name. Modify this by assigning attribute: "<desired name>".

    • reflect: boolean:默认情况下,更新后的 prop 值不会反映回 DOM。要启用此行为,请设置 reflect: true

      ¥reflect: boolean: By default, updated prop values do not reflect back to the DOM. To enable this behavior, set reflect: true.

    • type: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object':在将属性值转换为 prop 值并将其反射回来时,默认情况下假定 prop 值为 String。这可能并不总是准确的。例如,对于数字类型,使用 type: "Number" 定义它。你不需要列出所有属性,未列出的属性将使用默认设置。

      ¥type: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object': While converting an attribute value to a prop value and reflecting it back, the prop value is assumed to be a String by default. This may not always be accurate. For instance, for a number type, define it using type: "Number" You don’t need to list all properties, those not listed will use the default settings.

  • extend:一个可选属性,需要一个函数作为其参数。它传递由 Svelte 生成的自定义元素类,并期望你返回自定义元素类。如果你对自定义元素的生命周期有非常具体的要求,或者想要增强类以例如使用 ElementInternals 实现更好的 HTML 表单集成,这将非常方便。

    ¥extend: an optional property which expects a function as its argument. It is passed the custom element class generated by Svelte and expects you to return a custom element class. This comes in handy if you have very specific requirements to the life cycle of the custom element or want to enhance the class to for example use ElementInternals for better HTML form integration.

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

...

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

¥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(...) 修饰符的样式

    ¥Styles are encapsulated, rather than merely scoped (unless you set shadow: "none"). This means that any non-component styles (such as you might have in a global.css file) will not apply to the custom element, including styles with the :global(...) modifier

  • 样式不是作为单独的 .css 文件提取出来,而是作为 JavaScript 字符串内联到组件中

    ¥Instead of being extracted out as a separate .css file, styles are inlined into the component as a JavaScript string

  • 自定义元素通常不适合服务器端渲染,因为在 JavaScript 加载之前影子 DOM 是不可见的

    ¥Custom elements are not generally suitable for server-side rendering, as the shadow DOM is invisible until JavaScript loads

  • 在 Svelte 中,开槽内容会延迟渲染。在 DOM 中,它会预渲染。换句话说,即使组件的 <slot> 元素位于 {#if ...} 块内,它也将始终被创建。类似地,在 {#each ...} 块中包含 <slot> 不会导致插槽内容被多次渲染

    ¥In Svelte, slotted content renders lazily. In the DOM, it renders eagerly. In other words, it will always be created even if the component’s <slot> element is inside an {#if ...} block. Similarly, including a <slot> in an {#each ...} block will not cause the slotted content to be rendered multiple times

  • 已弃用的 let: 指令无效,因为自定义元素无法将数据传递给填充插槽的父组件

    ¥The deprecated let: directive has no effect, because custom elements do not have a way to pass data to the parent component that fills the slot

  • 需要 Polyfill 来支持旧版浏览器

    ¥Polyfills are required to support older browsers

  • 你可以在自定义元素内的常规 Svelte 组件之间使用 Svelte 的上下文功能,但不能跨自定义元素使用它们。换句话说,你不能在父自定义元素上使用 setContext,并在子自定义元素中使用 getContext 读取它。

    ¥You can use Svelte’s context feature between regular Svelte components within a custom element, but you can’t use them across custom elements. In other words, you can’t use setContext on a parent custom element and read that with getContext in a child custom element.

  • 不要声明以 on 开头的属性或特性,因为它们的用法将被解释为事件监听器。换句话说,Svelte 将 <custom-element oneworld={true}></custom-element> 视为 customElement.addEventListener('eworld', true)(而不是 customElement.oneworld = true

    ¥Don’t declare properties or attributes starting with on, as their usage will be interpreted as an event listener. In other words, Svelte treats <custom-element oneworld={true}></custom-element> as customElement.addEventListener('eworld', true) (and not as customElement.oneworld = true)

上一页 下一页