跳至主要內容

編譯器和 API

自訂元素 API

在 GitHub 上編輯此頁面

Svelte 組件也可以使用 customElement: true 編譯器選項編譯為自訂元素(又稱 Web 組件)。您應該使用 <svelte:options> 元素為組件指定標籤名稱。

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

<!-- in Svelte 3, do this instead:
<svelte:options tag="my-element" />
-->

<script>
	export let name = 'world';
</script>

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

您可以省略任何您不想要公開的內部組件的標籤名稱,並像一般的 Svelte 組件一樣使用它們。組件的使用者仍然可以在需要時使用包含自訂元素建構函式的靜態 element 屬性來命名它,而當 customElement 編譯器選項為 true 時,此屬性可用。

ts
import MyElement from './MyElement.svelte';
customElements.define('my-element', MyElement.element);
// In Svelte 3, do this instead:
// customElements.define('my-element', MyElement);

定義自訂元素後,它可以用作一般的 DOM 元素

ts
document.body.innerHTML = `
<my-element>
<p>This is some slotted content</p>
</my-element>
`;

預設情況下,自訂元素會使用 accessors: true 編譯,這表示任何 props 都會公開為 DOM 元素的屬性(以及在可能的情況下,可以作為屬性讀取/寫入)。

若要防止這種情況,請將 accessors={false} 新增至 <svelte:options>

ts
const el = document.querySelector('my-element');
// get the current value of the 'name' prop
console.log(el.name);
// set a new value, updating the shadow DOM
el.name = 'everybody';

組件生命週期

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

建立自訂元素時,它所包裝的 Svelte 組件並不會 立即建立。它只會在呼叫 connectedCallback 之後的下一個滴答中建立。指派給自訂元素(在它插入 DOM 之前)的屬性會暫時儲存,然後在建立組件時設定,因此它們的值不會遺失。不過,這不適用於呼叫自訂元素上的已匯出函式,它們只在元素掛載後才可用。如果您需要在建立組件之前呼叫函式,您可以使用 extend 選項 來解決此問題。

使用 Svelte 編寫的客製元素建立或更新時,影子 DOM 會在下次的 tick 反映值,而不是立即反映。這樣可以批次處理更新,而暫時(但同步)將元素從 DOM 分離的 DOM 移動不會導致內部元件卸載。

在呼叫 disconnectedCallback 後的下次 tick 中會銷毀內部 Svelte 元件。

元件選項

自 Svelte 4 起,在建立客製元素時,你可以透過在 <svelte:options> 中將 customElement 定義為物件來調整多個面向。此物件可能包含下列屬性

  • tag:客製元素名稱的強制 tag 屬性
  • shadow:可以設定為 "none" 的選用屬性,以放棄建立影子根。請注意,樣式不再封裝,而且你無法使用插槽
  • props:修改元件屬性特定詳細資料和行為的選用屬性。它提供下列設定
    • attribute: string:若要更新客製元素的 prop,你有兩個選擇:設定客製元素參考上的屬性,如上所示,或使用 HTML 屬性。對於後者,預設屬性名稱是小寫的屬性名稱。透過指定 attribute: "<desired name>" 來修改此設定。
    • reflect: boolean:預設情況下,更新的 prop 值不會反映回 DOM。若要啟用此行為,請設定 reflect: true
    • type: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object':在將屬性值轉換為 prop 值並反映回 DOM 時,預設會假設 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>
	export let elementIndex;
	export let attachedInternals;
	// ...
	function check() {
		attachedInternals.checkValidity();
	}
</script>

...

注意事項和限制

自訂元素可以是將元件封裝成非 Svelte 應用程式中可使用的元件的實用方式,因為它們會與原生 HTML 和 JavaScript 以及 大多數架構 搭配使用。不過,有一些重要的差異需要了解

  • 樣式是封裝的,而不是僅範圍化(除非您設定 shadow: "none")。這表示任何非元件樣式(例如您可能在 global.css 檔案中擁有的樣式)都不會套用至自訂元素,包含具有 :global(...) 修飾詞的樣式
  • 樣式並未作為一個獨立的 .css 檔案萃取出來,而是作為 JavaScript 字串內嵌到元件中
  • 自訂元素通常不適合伺服器端渲染,因為在 JavaScript 載入之前,影子 DOM 是不可見的
  • 在 Svelte 中,插槽內容會延遲渲染。在 DOM 中,它會立即渲染。換句話說,即使元件的 <slot> 元素位於 {#if ...} 區塊內,它也會一直被建立。類似地,在 {#each ...} 區塊中包含 <slot> 也不會導致插槽內容被渲染多次
  • let: 指令沒有作用,因為自訂元素沒有辦法將資料傳遞給填滿插槽的父元件
  • 需要使用 Polyfill 來支援較舊的瀏覽器
  • 你可以在自訂元素中的常規 Svelte 元件之間使用 Svelte 的 context 功能,但無法在自訂元素之間使用。換句話說,你無法在父自訂元素上使用 setContext,然後在子自訂元素中使用 getContext 讀取它。