Web コンポーネントはしばらく前から存在しており、再利用可能なカスタム要素を作成する標準化された方法を約束しています。 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 コンポーネントを適切にサポートしている場合は、ラッパー コンポーネントを作成する必要はありません。
もう 1 つの例は React です。これらの問題に対処した React v19 がリリースされました。ただし、まだ v18 を使用している場合は、v18 は Web コンポーネントを完全にはサポートしていないことに注意してください。そこで、React v18 で Web コンポーネントを操作するときに直面する可能性のある問題をいくつか紹介します。これは Lit ドキュメントから直接引用したものです。
「React は、すべての JSX プロパティが HTML 要素の属性にマップされることを前提としており、プロパティを設定する方法を提供しません。これにより、複雑なデータ (オブジェクト、配列、関数など) を Web コンポーネントに渡すことが困難になります。」
「React はまた、すべての DOM イベントに対応する「イベント プロパティ」 (onclick、onmousemove など) があることを前提としており、addEventListener() を呼び出す代わりにそれらを使用します。これは、より複雑な Web コンポーネントを適切に使用するには、多くの場合、使用する必要があることを意味します。 ref() と命令型コード。"
React v18 の場合、Lit はプロパティの設定とイベントのリッスンに関する問題を解決するため、ラッパー コンポーネントを使用することをお勧めします。
これは、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} />
幸いなことに、React v19 ではラッパー コンポーネントを作成する必要がなくなりました。やったー!
マイクロ フロントエンドでの Web コンポーネントの使用により、興味深い課題が明らかになりました。
重大な問題の 1 つは、カスタム要素レジストリのグローバルな性質です。
マイクロ フロントエンドを使用しており、Web コンポーネントを使用して各アプリ間で UI 要素を再利用する予定がある場合、このエラーが発生する可能性が高くなります。
@NgModule({ schemas: [CUSTOM_ELEMENTS_SCHEMA], }) export class MyModule {}
このエラーは、既に使用されている名前でカスタム要素を登録しようとすると発生します。マイクロ フロントエンドの各アプリは同じindex.html ファイルを共有し、各アプリがカスタム要素を定義しようとするため、これはマイクロ フロントエンドでは一般的です。
これに対処するための Scoped Custom Element Registries と呼ばれる提案がありますが、ETA がないため、残念ながらポリフィルを使用する必要があります。
ポリフィルを使用しない場合の回避策の 1 つは、名前の競合を避けるためにプレフィックスを付けてカスタム要素を手動で登録することです。
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
これは迅速な短期的な解決策として機能しますが、開発者エクスペリエンスが最適ではないことに気付くかもしれないため、スコープ付きカスタム要素レジストリ ポリフィルを使用することをお勧めします。
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 {}
これは、ルーティングがフレームワークではなくブラウザによって処理されるためです。フレームワークはこれらのイベントに介入し、フレームワーク レベルでルーティングを処理する必要があります。ただし、イベントはシャドウ 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
これは単純に見えるかもしれませんが、コンポーネントのネストが深くなるほど、難しくなります。また、コンポーネントがネストされている場合、ツールチップの操作がより困難になる可能性があります。特に、深くネストされた要素をターゲットにして、その下にツールチップを表示できるようにする必要がある場合。
私が発見したのは、スロットを使用するとコンポーネントがより小さく、より柔軟になり、メンテナンスも容易になるということです。したがって、スロットを優先し、シャドー ダムのネストは避けてください。
スロットは 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 フレームワークの急速な開発サイクルに比べて進化に時間がかかる可能性があるという事実が原因である可能性があります。
その結果、開発者は特定の機能を待つことになったり、他のフレームワークですぐに利用できる回避策を実装しなければならなくなったりする可能性があります。
この例としては、JS の CSS をスタイル設定のデフォルト オプションとして使用する Lit があります。 JS フレームワークの CSS にはパフォーマンスの問題があることは長い間知られていました
追加の実行時オーバーヘッドが発生することが多いためです。そこで、ランタイム ベースのソリューションに切り替えた、JS フレームワークで新しい CSS が登場し始めました。
Lit の JS ソリューション内の CSS は依然としてランタイムベースです。
もう 1 つの例はシグナルです。現在の Lit のデフォルトの動作では、@property デコレータを追加することでクラス プロパティに反応性を追加します。
ただし、プロパティが変更されると、コンポーネント全体の再レンダリングがトリガーされます。シグナルを使用すると、シグナルに依存するコンポーネントの一部のみが更新されます。
これは、UI を操作する場合により効率的です。非常に効率的であるため、これを JavaScript に追加する新しい提案 (TC39) があります。
現在、Lit は Signals を使用するためのパッケージを提供していますが、Vue や Solid などの他のフレームワークが既に何年もこれを行っているため、これはデフォルトの反応性ではありません。
シグナルがウェブ標準の一部となるまで、あと数年はシグナルがデフォルトの反応性として表示されることはおそらくないでしょう。
前の注意事項「9. スロット付き要素は常に dom 内にある」に関連するさらに別の例です。 Svelte の作成者である Rich Harris がこれについて話しました
5 年前のブログ投稿「私が Web コンポーネントを使用しない理由」
彼は、Svelte v2 でスロット付きコンテンツを積極的にレンダリングする方法について、Web 標準アプローチをどのように採用したかについて語ります。しかし、彼らはそこから離れなければなりませんでした
Svelte 3 では、それが開発者にとって非常に大きな不満点だったためです。彼らは、ほとんどの場合、スロット化されたコンテンツを遅延レンダリングする必要があることに気づきました。
Vuejs などの他のフレームワークがすでにこれをサポートしている場合、Web コンポーネントではデータをスロットに渡す簡単な方法がないなど、さらに多くの例を思いつくことができます。しかし、ここでの主なポイントは次のとおりです
Web コンポーネントは Web 標準に依存しているため、Web 標準に依存しないフレームワークよりもはるかに遅い機能を採用します。
Web 標準に依存しないことで、革新を起こし、より良いソリューションを考え出すことができます。
Web コンポーネントは、再利用可能でカプセル化されたカスタム要素を作成する強力な方法を提供します。ただし、これまで説明してきたように、開発者が作業する際に直面する可能性のある注意点や課題がいくつかあります。フレームワークの非互換性、マイクロ フロントエンドでの使用、Shadow DOM の制限、イベントのリターゲットの問題、スロット、機能導入の遅さなどはすべて、慎重な検討が必要な領域です。
これらの課題にもかかわらず、Web コンポーネントの真のカプセル化、移植性、フレームワークの独立性などの利点により、Web コンポーネントは現代の Web 開発において貴重なツールとなっています。エコシステムが進化し続けるにつれて、これらの警告に対処する改善や新しいソリューションが登場することが期待されます。
Web コンポーネントを検討している開発者にとって、これらの長所と短所を比較検討し、この分野の最新の進歩について常に最新の情報を入手することが重要です。適切なアプローチと理解があれば、Web コンポーネントは開発ツールキットへの強力な追加機能となります。
以上がWeb コンポーネントを使用するときに直面する可能性のある注意点の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。