自定义元素
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.
customElements.CustomElementRegistry.define(name: string, constructor: CustomElementConstructor, options?: ElementDefinitionOptions): void
define('my-element', const MyElement: LegacyComponentType
MyElement.element);
一旦自定义元素被定义,它就可以作为常规 DOM 元素一样使用:
var document: Document
document.Document.body: HTMLElement
Specifies the beginning and end of the document body.
body.InnerHTML.innerHTML: string
innerHTML = `
<my-element>
<p>这是一些插槽内容</p>
</my-element>
`;
任何 props 都会作为 DOM 元素的属性暴露出来(并且在可能的情况下作为属性可读/可写)。
const const el: Element | null
el = var document: Document
document.ParentNode.querySelector<Element>(selectors: string): Element | null (+4 overloads)
Returns the first element that is a descendant of node that matches selectors.
querySelector('my-element');
// 获取 'name' 属性的当前值
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
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.
log(const el: Element | null
el.name);
// 设置新值,更新 shadow DOM
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 4 起,你可以通过在 <svelte:options>
中定义 customElement
为一个对象来定制多个方面。这个对象可以包含以下属性:
tag: string
:自定义元素名称的可选tag
属性。如果设置,则在导入此组件时,会在文档的customElements
注册表中定义具有此标记名称的自定义元素。shadow
:一个可选属性,可以设置为"none"
以避免创建 shadow root。注意,样式就不再被封装,并且你不能使用插槽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) => {
// 扩展类,以便让它参与 HTML 表单
return class extends customElementConstructor {
static formAssociated = true;
constructor() {
super();
this.attachedInternals = this.attachInternals();
}
// 在这里添加函数,而不是在下面的组件中
// 这样它始终可用,而不仅仅是在内部 Svelte 组件
// 挂载时才可用
randomIndex() {
this.elementIndex = Math.random();
}
};
}
}}
/>
<script>
let { elementIndex, attachedInternals } = $props();
// ...
function check() {
attachedInternals.checkValidity();
}
</script>
...
注意事项和限制
自定义元素可以作为一种有用的方式来打包组件以在非 Svelte 应用程序中使用,因为它们可以与原生 HTML 和 JavaScript 以及大多数框架一起工作。然而,有一些重要的差异需要注意:
- 样式是封装的,而不是仅仅 scoped 的(除非你设置
shadow: "none"
)。这意味着任何非组件样式(比如你在global.css
文件中的样式)都不会应用到自定义元素上,包括带有:global(...)
修饰符的样式 - 样式被内联到组件中作为 JavaScript 字符串,而不是被提取出来作为单独的 .css 文件。
- 自定义元素通常不适合服务端渲染,因为在 JavaScript 加载之前,shadow DOM 是不可见的
- 在 Svelte 中,插槽内容是延迟渲染的。在 DOM 中,它是立即渲染的。换句话说,即使组件的
<slot>
元素在{#if ...}
块内,它也会始终被创建。类似地,在{#each ...}
块中包含<slot>
不会导致插槽内容被多次渲染 - 已废弃的
let:
指令没有效果,因为自定义元素没有办法将数据传递给填充插槽的父组件 - 需要 polyfills 来支持较旧的浏览器
- 你可以在自定义元素内的常规 Svelte 组件之间使用 Svelte 的上下文功能,但不能在自定义元素之间使用。换句话说,您不能在父自定义元素上使用
setContext
并在子自定义元素中用getContext
读取它。