跳至主要內容

自訂元素

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

任何 props 都會作為 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 就無法知道要將哪些 props 作為 DOM 元素上的屬性公開。

元件生命週期

自訂元素是使用包裝器方法從 Svelte 元件建立的。這表示內部的 Svelte 元件並不知道它是自訂元素。自訂元素包裝器負責適當地處理其生命週期。

當建立自訂元素時,它所包裝的 Svelte 元件不會立即建立。它只會在 connectedCallback 被調用後的下一個刻度中建立。在將自訂元素插入 DOM 之前分配給它的屬性會暫時儲存,然後在元件建立時設定,因此它們的值不會遺失。但這不適用於調用自訂元素上匯出的函數,它們只有在元素掛載後才可用。如果您需要在元件建立之前調用函數,您可以使用 extend 選項來解決這個問題。

當使用 Svelte 撰寫的自訂元素被建立或更新時,陰影 DOM 將會在下一個刻度中反映該值,而不是立即反映。這樣可以批次更新,並且 DOM 移動(暫時但同步地)將元素從 DOM 中分離出來不會導致內部元件被卸載。

內部的 Svelte 元件會在 disconnectedCallback 被調用後的下一個刻度中被銷毀。

元件選項

在建構自訂元素時,您可以透過在 <svelte:options> 中將 customElement 定義為物件(自 Svelte 4 起)來調整多個方面。此物件可能包含以下屬性

  • tag: string:自訂元素名稱的可選 tag 屬性。如果設定此屬性,則會在使用此元件時在文件的 customElements 註冊表中定義具有此標籤名稱的自訂元素。
  • shadow:可選屬性,可以設定為 "none" 以放棄陰影根建立。請注意,樣式將不再被封裝,並且您不能使用 slots
  • props:可選屬性,用於修改元件屬性的某些詳細資訊和行為。它提供以下設定
    • attribute: string:若要更新自訂元素的 prop,您有兩種替代方案:要麼如上所示在自訂元素的參考上設定屬性,要麼使用 HTML 屬性。對於後者,預設的屬性名稱是小寫的屬性名稱。透過指定 attribute: "<想要的名稱>" 來修改此名稱。
    • 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>
	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 上編輯此頁面

上一頁 下一頁