Web 元件已經存在了一段時間,承諾提供一種標準化的方法來建立可重複使用的自訂元素。很明顯,雖然 Web 元件已經取得了顯著的進步,但開發人員在使用它們時仍然可能面臨一些注意事項。本部落格將探討其中 10 個注意事項。
如果您正在決定是否在專案中使用 Web 元件。重要的是要考慮您選擇的框架是否完全支援 Web 元件,否則您可能會遇到一些令人不快的警告。
例如,要在 Angular 中使用 Web 元件,需要將 CUSTOM_ELEMENTS_SCHEMA 新增至模組導入。
@NgModule({ schemas: [CUSTOM_ELEMENTS_SCHEMA], }) export class MyModule {}
使用 CUSTOM_ELEMENTS_SCHEMA 的問題是 Angular 將選擇退出模板中自訂元素的類型檢查和智慧感知。 (見問題)
要解決此問題,您可以建立一個 Angular 包裝器組件。
這是一個範例。
@Component({ selector: 'some-web-component-wrapper', template: '<some-web-component [someProperty]="someClassProperty"></some-web-component> }) export class SomeWebComponentWrapper { @Input() someClassProperty: string; } @NgModule({ declarations: [SomeWebComponentWrapper], exports: [SomeWebComponentWrapper], schemas: [CUSTOM_ELEMENTS_SCHEMA] }) export class WrapperModule {}
這可行,但手動建立它們不是一個好主意。因為這會產生大量的維護工作,我們可能會遇到 api 不同步的問題。為了讓這不那麼無聊。 Lit(請參閱此處)和 Stencil(請參閱此處)都提供了一個 cli 來自動創建它們。然而,首先需要創建這些包裝器組件是額外的開銷。如果您選擇的框架正確支援 Web 元件,您不必建立包裝器元件。
另一個例子是 React。現在 React v19 剛剛發布,解決了這些問題。但是,如果您仍在使用 v18,請注意 v18 並不完全支援 Web 元件。因此,您在使用 React v18 中的 Web 元件時可能會遇到一些問題。這直接取自 Lit 文件。
對於 React v18 Lit 建議使用他們的包裝元件,因為它們解決了設定屬性和監聽事件的問題。「React 假設所有 JSX 屬性都對應到 HTML 元素屬性,並且不提供設定屬性的方法。這使得將複雜資料(如物件、陣列或函數)傳遞給 Web 元件變得困難。」
「React 也假設所有DOM 事件都有對應的「事件屬性」(onclick、onmousemove 等),並使用這些屬性而不是呼叫addEventListener()。這意味著要正確使用更複雜的Web 元件,您通常必須使用ref() 和命令式程式碼。
這是使用 Lit 的 React 包裝組件的範例。
import React from 'react'; import { createComponent } from '@lit/react'; import { MyElement } from './my-element.js'; export const MyElementComponent = createComponent({ tagName: 'my-element', elementClass: MyElement, react: React, events: { onactivate: 'activate', onchange: 'change', }, });
<MyElementComponent active={isActive} onactivate={(e) => setIsActive(e.active)} onchange={handleChange} />
在微前端中使用 Web 元件揭示了一個有趣的挑戰:
一個重要的問題是自訂元素註冊表的全域性質:
如果您使用微前端並計劃使用 Web 元件在每個應用程式中重複使用 UI 元素,您很可能會遇到此錯誤。
@NgModule({ schemas: [CUSTOM_ELEMENTS_SCHEMA], }) export class MyModule {}
嘗試使用已使用的名稱註冊自訂元素時會發生此錯誤。這在微前端中很常見,因為微前端中的每個應用程式共享相同的 index.html 文件,並且每個應用程式都嘗試定義自訂元素。
有一項提案可以解決這個問題,稱為“範圍自訂元素註冊表”,但沒有預計到達時間,因此不幸的是,您需要使用填充。
如果您不使用polyfill,一種解決方法是使用前綴手動註冊自訂元素,以避免命名衝突。
要在 Lit 中執行此操作,您可以避免使用自動註冊自訂元素的 @customElement 裝飾器。然後為 tagName 新增靜態屬性。
之前
@Component({ selector: 'some-web-component-wrapper', template: '<some-web-component [someProperty]="someClassProperty"></some-web-component> }) export class SomeWebComponentWrapper { @Input() someClassProperty: string; } @NgModule({ declarations: [SomeWebComponentWrapper], exports: [SomeWebComponentWrapper], schemas: [CUSTOM_ELEMENTS_SCHEMA] }) export class WrapperModule {}
之後
import React from 'react'; import { createComponent } from '@lit/react'; import { MyElement } from './my-element.js'; export const MyElementComponent = createComponent({ tagName: 'my-element', elementClass: MyElement, react: React, events: { onactivate: 'activate', onchange: 'change', }, });
然後在每個應用程式中,您可以使用應用程式名稱的前綴定義自訂元素。
<MyElementComponent active={isActive} onactivate={(e) => setIsActive(e.active)} onchange={handleChange} />
然後要使用自訂元素,您可以將其與新前綴一起使用。
Uncaught DOMException: Failed to execute 'define' on 'CustomElementRegistry': the name "foo-bar" has already been used with this registry
這是一個快速的短期解決方案,但是您可能會注意到這不是最好的開發人員體驗,因此建議使用範圍自訂元素註冊表polyfill。
Shadow DOM 在提供封裝的同時,也帶來了自己的一系列挑戰:
Shadow dom 透過提供封裝來運作。它可以防止樣式從組件中洩漏。它還可以防止全域樣式定位元件的 Shadow dom 內的元素。但是,如果繼承了元件外部的樣式,這些樣式仍然可能會洩漏。
這是一個例子。
@customElement('simple-greeting') export class SimpleGreeting extends LitElement { render() { return html`<p>Hello world!</p>`; } }
export class SimpleGreeting extends LitElement { static tagName = 'simple-greeting'; render() { return html`<p>Hello world!</p>`; } }
當我們點擊時該按鈕會發出一個冒泡的組合事件。
組件-a
[SimpleGreeting].forEach((component) => { const newTag = `app1-${component.tagName}`; if (!customElements.get(newTag)) { customElements.define(newTag, SimpleGreeting); } });
由於事件來自元件 b,您可能會認為目標是元件 b 或按鈕。然而,事件被重新定位,因此目標成為組件 a。
因此,如果您需要知道事件是否來自
如果連結在 Shadow dom 中使用,如本例所示,它將在您的應用程式中觸發整個頁面重新載入。
@NgModule({ schemas: [CUSTOM_ELEMENTS_SCHEMA], }) export class MyModule {}
這是因為路由是由瀏覽器而不是您的框架處理的。框架需要介入這些事件並在框架層級處理路由。然而,由於事件在 Shadow dom 中重新定位,這使得框架這樣做更具挑戰性,因為它們無法輕鬆存取錨元素。
要解決此問題,我們可以在 上設定一個事件處理程序這將停止事件的傳播並發出一個新事件。新事件需要冒泡並組合。此外,我們還需要存取詳細資訊
到 我們可以從 e.currentTarget 取得實例。
@Component({ selector: 'some-web-component-wrapper', template: '<some-web-component [someProperty]="someClassProperty"></some-web-component> }) export class SomeWebComponentWrapper { @Input() someClassProperty: string; } @NgModule({ declarations: [SomeWebComponentWrapper], exports: [SomeWebComponentWrapper], schemas: [CUSTOM_ELEMENTS_SCHEMA] }) export class WrapperModule {}
在消費端,您可以設定一個全域事件監聽器來監聽此事件並透過呼叫框架特定的路由函數來處理路由。
建置 Web 元件時。您可以決定將其他 Web 元件放入插槽或將它們嵌套在另一個 Web 元件中。這是一個例子。
開槽圖示
import React from 'react'; import { createComponent } from '@lit/react'; import { MyElement } from './my-element.js'; export const MyElementComponent = createComponent({ tagName: 'my-element', elementClass: MyElement, react: React, events: { onactivate: 'activate', onchange: 'change', }, });
巢狀圖示
<MyElementComponent active={isActive} onactivate={(e) => setIsActive(e.active)} onchange={handleChange} />
如果您決定巢狀元件,這可能會使查詢巢狀元件變得更加困難。特別是如果您有一個 QA 團隊需要建立端對端測試,因為他們需要針對頁面上的特定元素。
例如,要存取 some-icon,我們需要先透過取得 some-banner 的shadowRoot 來存取它,然後在該影子根內建立一個新查詢。
Uncaught DOMException: Failed to execute 'define' on 'CustomElementRegistry': the name "foo-bar" has already been used with this registry
這可能看起來很簡單,但元件嵌套得越深,它就會變得越來越困難。此外,如果您的元件是巢狀的,這可能會使工具提示的使用變得更加困難。特別是如果您需要定位深層嵌套的元素,以便可以在其下方顯示工具提示。
我發現使用插槽使我們的組件更小、更靈活,也更易於維護。所以更喜歡插槽,避免巢狀 Shadow dom。
插槽提供了一種組合 UI 元素的方法,但它們在 Web 元件中存在局限性。
::slotted 選擇器僅適用於插槽的直接子級,限制了它在更複雜場景中的用處。
這是一個例子。
@customElement('simple-greeting') export class SimpleGreeting extends LitElement { render() { return html`<p>Hello world!</p>`; } }
export class SimpleGreeting extends LitElement { static tagName = 'simple-greeting'; render() { return html`<p>Hello world!</p>`; } }
[SimpleGreeting].forEach((component) => { const newTag = `app1-${component.tagName}`; if (!customElements.get(newTag)) { customElements.define(newTag, SimpleGreeting); } });
Web 元件在採用新功能和最佳實踐方面通常落後於 Vue、React、Svelte 和 Solid 等流行框架。
這可能是由於 Web 元件依賴瀏覽器實作和標準,與現代 JavaScript 框架的快速開發週期相比,這可能需要更長的時間才能發展。
因此,開發人員可能會發現自己正在等待某些功能,或者必須實現其他框架中隨時可用的解決方法。
Lit 的一些例子是在 JS 中使用 CSS 作為樣式的預設選項。人們很早就知道 JS 框架中的 CSS 有效能問題
因為它們經常引入額外的運行時開銷。因此,我們開始在 JS 框架中看到更新的 CSS,這些框架切換到基於零運行時的解決方案。
Lit 的 CSS in JS 解決方案仍然是基於運行時的。
另一個例子是訊號。目前 Lit 中的預設行為是我們透過添加 @property 裝飾器來為類別屬性添加反應性。
但是,當屬性發生變更時,它將觸發整個元件重新渲染。使用訊號,只有依賴訊號的元件的一部分才會更新。
這對於使用 UI 來說更有效率。如此高效,以至於有一個新提案(TC39)將其添加到 JavaScript 中。
現在 Lit 確實提供了一個使用 Signals 的套件,但它並不是預設的反應性,而 Vue 和 Solid 等其他框架已經這樣做了很多年。
在訊號成為網路標準之前,我們很可能在幾年內不會將訊號視為預設反應性。
另一個例子與我之前的警告「9. 開槽元素總是在 dom 中」相關。 Svelte 的創辦人 Rich Harris 談到了這個
在他 5 年前題為「為什麼我不使用 Web 元件」的部落格文章中。
他談到了他們如何採用 Web 標準方法來在 Svelte v2 中快速呈現分槽內容。然而,他們不得不遠離它
在 Svelte 3 中,因為這對開發人員來說是一個很大的挫折點。他們注意到大多數時候您希望開槽內容延遲渲染。
我可以舉出更多範例,例如在 Web 元件中,當 Vuejs 等其他框架已經支援此操作時,就沒有簡單的方法將資料傳遞到插槽。但這裡的主要要點是
Web 元件由於依賴 Web 標準,因此採用的功能比不依賴 Web 標準的框架慢得多。
透過不依賴網路標準,我們可以創新並提出更好的解決方案。
Web 元件提供了一種強大的方法來建立可重複使用和封裝的自訂元素。然而,正如我們所探討的,開發人員在使用它們時可能會面臨一些注意事項和挑戰。例如框架不相容、微前端的使用、Shadow DOM 的限制、事件重定向問題、插槽以及緩慢的功能採用都是需要仔細考慮的領域。
儘管存在這些挑戰,Web 元件的優點(例如真正的封裝、可移植性和框架獨立性)使它們成為現代 Web 開發中的寶貴工具。隨著生態系統的不斷發展,我們預計會看到解決這些問題的改進和新的解決方案。
對於考慮 Web Components 的開發人員來說,權衡這些利弊並隨時了解該領域的最新進展至關重要。透過正確的方法和理解,Web 元件可以成為您的開發工具包的強大補充。
以上是使用 Web 元件時可能面臨的注意事項的詳細內容。更多資訊請關注PHP中文網其他相關文章!