Webkomponenten gibt es schon seit einiger Zeit und versprechen eine standardisierte Möglichkeit, wiederverwendbare benutzerdefinierte Elemente zu erstellen. Es ist klar, dass Webkomponenten zwar erhebliche Fortschritte gemacht haben, Entwickler bei der Arbeit mit ihnen jedoch immer noch mit einigen Einschränkungen konfrontiert sein können. In diesem Blog werden 10 dieser Vorbehalte untersucht.
Wenn Sie entscheiden, ob Sie Webkomponenten in Ihrem Projekt verwenden möchten oder nicht. Es ist wichtig zu überlegen, ob Webkomponenten in dem von Ihnen gewählten Framework vollständig unterstützt werden, da Sie sonst möglicherweise auf einige unangenehme Vorbehalte stoßen.
Um beispielsweise Webkomponenten in Angular zu verwenden, ist es erforderlich, CUSTOM_ELEMENTS_SCHEMA zum Modulimport hinzuzufügen.
@NgModule({ schemas: [CUSTOM_ELEMENTS_SCHEMA], }) export class MyModule {}
Das Problem bei der Verwendung von CUSTOM_ELEMENTS_SCHEMA besteht darin, dass Angular die Typprüfung und Intellisense für benutzerdefinierte Elemente in den Vorlagen deaktiviert. (siehe Ausgabe)
Um dieses Problem zu umgehen, könnten Sie eine Angular-Wrapper-Komponente erstellen.
Hier ist ein Beispiel, wie das aussehen würde.
@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 {}
Das funktioniert, aber es ist keine gute Idee, diese manuell zu erstellen. Da dies einen hohen Wartungsaufwand verursacht und es zu Problemen mit der Synchronisierung der API kommen kann. Um es weniger mühsam zu machen. Sowohl Lit (siehe hier) als auch Schablone (siehe hier) bieten eine CLI, um diese automatisch zu erstellen. Die Notwendigkeit, diese Wrapper-Komponenten überhaupt zu erstellen, verursacht jedoch zusätzlichen Aufwand. Wenn das Framework Ihrer Wahl Webkomponenten ordnungsgemäß unterstützt, sollten Sie keine Wrapper-Komponenten erstellen müssen.
Ein weiteres Beispiel ist React. Jetzt wurde gerade React v19 veröffentlicht, das diese Probleme behebt. Wenn Sie jedoch noch Version 18 verwenden, beachten Sie bitte, dass Version 18 Webkomponenten nicht vollständig unterstützt. Hier sind einige Probleme, die bei der Arbeit mit Webkomponenten in React v18 auftreten können. Dies ist direkt den Lit-Dokumenten entnommen.
„React geht davon aus, dass alle JSX-Eigenschaften HTML-Elementattributen zugeordnet sind, und bietet keine Möglichkeit, Eigenschaften festzulegen. Dies erschwert die Übergabe komplexer Daten (wie Objekte, Arrays oder Funktionen) an Webkomponenten.“
„React geht außerdem davon aus, dass alle DOM-Ereignisse über entsprechende „Ereigniseigenschaften“ (onclick, onmousemove usw.) verfügen, und verwendet diese, anstatt addEventListener() aufzurufen. Dies bedeutet, dass Sie häufig komplexere Webkomponenten verwenden müssen, um sie ordnungsgemäß zu verwenden ref() und Imperativcode.“
Für React v18 empfiehlt Lit die Verwendung der Wrapper-Komponenten, da diese die Probleme beim Festlegen der Eigenschaften und beim Abhören von Ereignissen für Sie beheben.
Hier ist ein Beispiel einer React-Wrapper-Komponente mit Lit.
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', }, });
Verwendung
<MyElementComponent active={isActive} onactivate={(e) => setIsActive(e.active)} onchange={handleChange} />
Glücklicherweise sollten Sie mit React v19 keine Wrapper-Komponenten mehr erstellen müssen. Juhu!
Der Einsatz von Webkomponenten in Micro-Frontends hat eine interessante Herausforderung offenbart:
Ein wesentliches Problem ist die globale Natur der Custom Elements Registry:
Wenn Sie ein Mikro-Frontend verwenden und planen, Webkomponenten zu verwenden, um UI-Elemente in jeder App wiederzuverwenden, wird dieser Fehler höchstwahrscheinlich auftreten.
@NgModule({ schemas: [CUSTOM_ELEMENTS_SCHEMA], }) export class MyModule {}
Dieser Fehler tritt auf, wenn versucht wird, ein benutzerdefiniertes Element mit einem Namen zu registrieren, der bereits verwendet wurde. Dies ist bei Mikro-Frontends üblich, da jede App in einem Mikro-Frontend dieselbe index.html-Datei verwendet und jede App versucht, die benutzerdefinierten Elemente zu definieren.
Es gibt einen Vorschlag namens „Scoped Custom Element Registries“, aber es gibt keine ETA, daher müssen Sie leider eine Polyfüllung verwenden.
Wenn Sie die Polyfüllung nicht verwenden, besteht eine Problemumgehung darin, die benutzerdefinierten Elemente manuell mit einem Präfix zu registrieren, um Namenskonflikte zu vermeiden.
Um dies in Lit zu tun, können Sie die Verwendung des @customElement-Dekorators vermeiden, der das benutzerdefinierte Element automatisch registriert. Fügen Sie dann eine statische Eigenschaft für tagName hinzu.
Vorher
@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 {}
Nachher
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', }, });
Dann definieren Sie in jeder App das benutzerdefinierte Element mit einem Präfix des App-Namens.
<MyElementComponent active={isActive} onactivate={(e) => setIsActive(e.active)} onchange={handleChange} />
Um dann das benutzerdefinierte Element zu verwenden, würden Sie es mit dem neuen Präfix verwenden.
Uncaught DOMException: Failed to execute 'define' on 'CustomElementRegistry': the name "foo-bar" has already been used with this registry
Dies funktioniert als schnelle, kurzfristige Lösung. Möglicherweise stellen Sie jedoch fest, dass dies nicht die beste Entwicklererfahrung ist. Daher wird empfohlen, die Polyfill-Datei „Scoped Custom Element Registry“ zu verwenden.
Das Shadow DOM bietet zwar eine Kapselung, bringt aber auch seine eigenen Herausforderungen mit sich:
Shadow Dom funktioniert durch die Bereitstellung einer Kapselung. Es verhindert, dass Stile aus der Komponente austreten. Es verhindert außerdem, dass globale Stile auf Elemente im Schattendom der Komponente abzielen. Allerdings können Stile von außerhalb der Komponente immer noch eindringen, wenn diese Stile geerbt werden.
Hier ist ein Beispiel.
@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>`; } }
Wenn wir auf die Schaltfläche
Komponente-a
[SimpleGreeting].forEach((component) => { const newTag = `app1-${component.tagName}`; if (!customElements.get(newTag)) { customElements.define(newTag, SimpleGreeting); } });
Da das Ereignis von Komponente B kommt, könnte man denken, dass das Ziel Komponente B oder die Schaltfläche ist. Das Ereignis wird jedoch neu ausgerichtet, sodass das Ziel zur Komponente a wird.
Wenn Sie also wissen möchten, ob ein Ereignis von der
@NgModule({ schemas: [CUSTOM_ELEMENTS_SCHEMA], }) export class MyModule {}
Das liegt daran, dass das Routing vom Browser und nicht von Ihrem Framework übernommen wird. Frameworks müssen in diese Ereignisse eingreifen und das Routing auf Framework-Ebene verwalten. Da Ereignisse jedoch im Shadow-Dom neu ausgerichtet werden, wird dies für Frameworks schwieriger, da sie keinen einfachen Zugriff auf das Ankerelement haben.
Um dieses Problem zu umgehen, können wir einen Event-Handler auf der Seite einrichten. Dadurch wird die Weitergabe des Ereignisses gestoppt und ein neues Ereignis ausgegeben. Die neue Veranstaltung muss sprudeln und gefasst werden. Auch im Detail benötigen wir Zugriff
zum Instanz, die wir von e.currentTarget erhalten können.
@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 {}
Auf der Konsumentenseite können Sie einen globalen Ereignis-Listener einrichten, der auf dieses Ereignis wartet und das Routing durch den Aufruf von Framework-spezifischen Routing-Funktionen übernimmt.
Beim Aufbau von Webkomponenten. Sie können entweder die Entscheidung treffen, andere Webkomponenten einzufügen oder sie in einer anderen zu verschachteln. Hier ist ein Beispiel.
geschlitztes Symbol
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', }, });
verschachteltes Symbol
<MyElementComponent active={isActive} onactivate={(e) => setIsActive(e.active)} onchange={handleChange} />
Wenn Sie sich entscheiden, die Komponente zu verschachteln, kann dies die Abfrage der verschachtelten Komponenten erschweren. Vor allem, wenn Sie ein QA-Team haben, das End-to-End-Tests erstellen muss, da es auf bestimmte Elemente auf der Seite abzielen muss.
Um beispielsweise auf ein bestimmtes Symbol zuzugreifen, müssen wir zuerst auf ein bestimmtes Banner zugreifen, indem wir dessen Schattenstamm greifen und dann eine neue Abfrage innerhalb dieses Schattenstamms erstellen.
Uncaught DOMException: Failed to execute 'define' on 'CustomElementRegistry': the name "foo-bar" has already been used with this registry
Das sieht vielleicht einfach aus, wird aber umso schwieriger, je tiefer Ihre Komponenten verschachtelt sind. Auch wenn Ihre Komponenten verschachtelt sind, kann dies die Arbeit mit Tooltips erschweren. Vor allem, wenn Sie auf ein tief verschachteltes Element zielen müssen, damit Sie den Tooltip darunter anzeigen können.
Was ich herausgefunden habe, ist, dass die Verwendung von Steckplätzen unsere Komponenten kleiner und flexibler macht, was auch einfacher wartbar ist. Bevorzugen Sie also Slots und vermeiden Sie die Verschachtelung von Schattendomen.
Slots bieten eine Möglichkeit zum Zusammenstellen von UI-Elementen, weisen jedoch Einschränkungen in Webkomponenten auf.
Der ::slotted-Selektor gilt nur für die direkten untergeordneten Elemente eines Slots, was seine Nützlichkeit in komplexeren Szenarien einschränkt.
Hier ist ein Beispiel.
@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); } });
Webkomponenten bleiben bei der Übernahme neuer Funktionen und Best Practices oft hinter beliebten Frameworks wie Vue, React, Svelte und Solid zurück.
Dies kann auf die Tatsache zurückzuführen sein, dass Webkomponenten auf Browserimplementierungen und -standards basieren, deren Entwicklung im Vergleich zu den schnellen Entwicklungszyklen moderner JavaScript-Frameworks länger dauern kann.
Infolgedessen warten Entwickler möglicherweise auf bestimmte Funktionen oder müssen Problemumgehungen implementieren, die in anderen Frameworks leicht verfügbar sind.
Einige Beispiele hierfür ist Lit, das CSS in JS als Standardoption für das Styling verwendet. Es ist seit langem bekannt, dass CSS in JS-Frameworks Leistungsprobleme aufweist
weil sie oft zusätzlichen Laufzeitaufwand mit sich brachten. Daher haben wir begonnen, neueres CSS in JS-Frameworks zu sehen, die auf Null-Laufzeit-basierte Lösungen umgestiegen sind.
Die CSS-in-JS-Lösung von Lit ist immer noch laufzeitbasiert.
Ein weiteres Beispiel sind Signale. Derzeit besteht das Standardverhalten in Lit darin, dass wir den Klasseneigenschaften Reaktivität hinzufügen, indem wir den @property-Dekorator hinzufügen.
Wenn die Eigenschaft jedoch geändert wird, wird die gesamte Komponente neu gerendert. Bei Signalen wird nur ein Teil der Komponente aktualisiert, die auf das Signal angewiesen ist.
Dies ist effizienter für die Arbeit mit Benutzeroberflächen. So effizient, dass es einen neuen Vorschlag (TC39) gibt, dies zu JavaScript hinzuzufügen.
Jetzt stellt Lit zwar ein Paket zur Verwendung von Signalen bereit, aber es ist nicht die Standardreaktivität, während andere Frameworks wie Vue und Solid dies bereits seit Jahren tun.
Wir werden Signale höchstwahrscheinlich erst in ein paar Jahren als Standardreaktivität sehen, bis Signale Teil der Webstandards sind.
Noch ein Beispiel, das sich auf meinen vorherigen Vorbehalt „9. Schlitzelemente befinden sich immer im Dom“ bezieht. Rich Harris, der Schöpfer von Svelte, hat darüber gesprochen
in seinem Blogbeitrag vor 5 Jahren mit dem Titel „Warum ich keine Webkomponenten verwende“.
Er spricht darüber, wie sie den Ansatz der Webstandards übernommen haben, um die Art und Weise zu bestimmen, wie geslotte Inhalte in Svelte v2 eifrig gerendert werden. Allerdings mussten sie davon abrücken
in Svelte 3, weil es für die Entwickler ein großer Frustrationspunkt war. Ihnen ist aufgefallen, dass die Slot-Inhalte in den meisten Fällen langsam gerendert werden sollen.
Ich kann mir weitere Beispiele einfallen lassen, zum Beispiel, dass es in Webkomponenten keine einfache Möglichkeit gibt, Daten an Slots zu übergeben, wenn andere Frameworks wie Vuejs dies bereits unterstützen. Aber das Wichtigste hier ist Folgendes:
Da Webkomponenten auf Webstandards basieren, übernehmen sie Funktionen viel langsamer als Frameworks, die nicht auf Webstandards basieren.
Indem wir uns nicht auf Webstandards verlassen, können wir innovativ sein und bessere Lösungen entwickeln.
Webkomponenten bieten eine leistungsstarke Möglichkeit, wiederverwendbare und gekapselte benutzerdefinierte Elemente zu erstellen. Wie wir jedoch untersucht haben, gibt es einige Vorbehalte und Herausforderungen, mit denen Entwickler bei der Arbeit mit ihnen konfrontiert sein können. B. Framework-Inkompatibilität, Verwendung in Mikro-Frontends, Einschränkungen des Shadow DOM, Probleme mit Event-Retargeting, Slots und langsame Funktionseinführung sind alles Bereiche, die sorgfältige Überlegungen erfordern.
Trotz dieser Herausforderungen machen die Vorteile von Webkomponenten, wie echte Kapselung, Portabilität und Framework-Unabhängigkeit, sie zu einem wertvollen Werkzeug in der modernen Webentwicklung. Während sich das Ökosystem weiterentwickelt, können wir mit Verbesserungen und neuen Lösungen rechnen, die diese Vorbehalte beheben.
Für Entwickler, die Webkomponenten in Betracht ziehen, ist es wichtig, diese Vor- und Nachteile abzuwägen und über die neuesten Fortschritte auf diesem Gebiet auf dem Laufenden zu bleiben. Mit dem richtigen Ansatz und Verständnis können Webkomponenten eine leistungsstarke Ergänzung Ihres Entwicklungs-Toolkits sein.
Das obige ist der detaillierte Inhalt vonVorbehalte, die bei der Arbeit mit Webkomponenten auftreten können. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!