Warum ich keine Webkomponenten verwende

Ich schreibe dies hauptsächlich für mich selbst in der Zukunft, damit ich einen Platz habe, an den ich mich wenden kann, wenn mich jemand fragt, warum ich skeptisch gegenüber Webkomponenten bin und warum Svelte nicht standardmäßig in Webkomponenten kompiliert wird. (Es kann jedoch in Webkomponenten kompiliert und in diese integriert werden, wie die hervorragende Bewertung von Custom Elements Everywhere zeigt. )


Keine der folgenden Aussagen sollte als Kritik an der harten Arbeit an Webkomponenten ausgelegt werden. Vielleicht habe ich auch in dieser Veröffentlichung Fehler gemacht. In diesem Fall würde ich gerne Änderungen vornehmen. Ich erkläre auch nicht, dass Sie keine Webkomponenten verwenden sollten. Sie haben ihren eigenen Anwendungsbereich. Ich erkläre nur, warum sie nicht für mich geeignet sind.


1. Fortschreitende Verbesserung


Dies mag eine altmodische Überzeugung sein, aber ich glaube, dass Websites so weit wie möglich ohne JavaScript funktionieren sollten. Webkomponenten ohne JS funktionieren nicht. Dies ist normal für Dinge, die von Natur aus interaktiv sind, wie z. B. benutzerdefinierte Formularelemente (<cool-datepicker>), aber nicht normal für die Site-Navigation. Oder stellen Sie sich eine <twitter-share> -Komponente vor, die die Logik zum Erstellen einer URL zum Senden an Twitter zusammenfasst . Ich könnte es auf Svelte implementieren , das mir den folgenden HTML-Code auf dem Server rendert:


 <a target="_blank" noreferrer href="..." class="svelte-1jnfxx"> Tweet this </a> 

Mit anderen Worten, das übliche <a> in seiner ganzen Pracht zur Verfügung.


Wenn JavaScript aktiviert ist, erfolgt eine schrittweise Verbesserung. Anstatt eine neue Registerkarte zu öffnen, wird ein kleines Popup-Fenster geöffnet. Aber auch ohne JS funktioniert die Komponente noch einwandfrei.


Im Fall einer HTML-Webkomponente würde dies ungefähr so ​​aussehen:


 <twitter-share text="..." url="..." via="..."/> 

... was nutzlos und ungeeignet ist, wenn JS blockiert ist oder aus irgendeinem Grund defekt ist oder der Benutzer einen alten Browser hat.


Darüber hinaus bietet class="svelte-1jnfxx" eine class="svelte-1jnfxx" ohne Shadow DOM. Das bringt uns zum nächsten Punkt.


2. CSS in, äh ... JS


Wenn Sie das Schatten-DOM zum Einkapseln von Stilen verwenden möchten, müssen Sie Ihr CSS in das <style> einfügen. Die einzige praktische Möglichkeit, dies zu tun, wenn Sie das Laden von blinkendem Inhalt (FOUC) vermeiden möchten, besteht darin, CSS als Zeichenfolge in JavaScript einzubetten, die den Rest der Logik Ihrer Webkomponente definiert.


Dies widerspricht dem Ratschlag zur Leistungsverbesserung, der lautet: "Bitte weniger JavaScript." Insbesondere die CSS-in-JS-Community wurde vielfach dafür kritisiert, dass sie keine CSS-Dateien für CSS verwendet, und hier sind wir wieder mit Webkomponenten.


In Zukunft können wir sowohl CSS-Module als auch konstruierbare Stylesheets verwenden , um dieses Problem zu lösen. Wir werden auch die Möglichkeit haben, die Interna des Shadow DOM durch ::theme und ::part . Aber hier war es nicht ohne Probleme.


3. Ermüdung der Plattform



Dies ist eine schmerzhafte Krone für mich - ich habe diese Dinge seit mehreren Jahren als „Die Zukunft“ beworben, aber um mit der Gegenwart Schritt zu halten , mussten wir die Plattform mit einer Reihe verschiedener Funktionen füllen, was die Lücke zwischen den Browsern vergrößert.

Zum Zeitpunkt des Schreibens waren unter https://crbug.com , dem Bug-Tracker von Chrome, 61.000 offene Bugs vorhanden, die die enorme Komplexität des Schreibens eines modernen Browsers belegen.


Jedes Mal, wenn wir der Plattform eine neue Funktion hinzufügen, erhöhen wir die Komplexität - schaffen das Potenzial für neue Fehler und verringern die Wahrscheinlichkeit, dass Chrome einen neuen Konkurrenten hat. Dies führt auch zu Schwierigkeiten für Entwickler, die aufgefordert werden, diese neuen Funktionen zu erlernen (von denen einige, wie z. B. HTML-Importe oder die Originalversion des Standards für benutzerdefinierte Elemente, außerhalb von Google keine Wurzeln geschlagen haben und derzeit entfernt werden).


4. Polyphile


Die Tatsache, dass Sie Polyfiles verwenden müssen, um ältere Browser zu unterstützen, trägt nicht zur Entwicklung der Situation bei. Und es hilft überhaupt nicht, dass Artikel in Constructable Stylesheets in Google geschrieben wurden (hi Jason!). Erwähnen Sie nicht, dass diese Funktion nur in Chrome verfügbar ist. (Alle drei Autoren der Spezifikation arbeiten für Google. Webkit scheint Zweifel an einigen Aspekten dieses Standards zu haben.)


5. Zusammensetzung


Es kann nützlich sein zu steuern, wann der Inhalt eines Slots gerendert werden soll. Stellen Sie sich vor, Sie haben <html-include> zum Laden zusätzlicher Inhalte, wenn diese sichtbar sind:


 <p>Toggle the section for more info:</p> <toggled-section> <html-include src="./more-info.html"/> </toggled-section> 

Auf einmal! Auch wenn wir den toggled-section noch nicht geöffnet toggled-section , der Browser jedoch bereits more-info.html angefordert more-info.html , zusammen mit allen Bildern und anderen Ressourcen, die dort vorhanden sind.


Dies liegt daran, dass der Inhalt der Slots im Voraus in den Webkomponenten gerendert wird. In der Realität stellt sich heraus, dass Sie in den meisten Fällen den Inhalt der Slots träge rendern möchten. Svelte v2 hat ein proaktives Redning-Modell eingeführt, um die Webstandards zu erfüllen. Dies war jedoch die Hauptursache für Unannehmlichkeiten. Wir konnten beispielsweise nichts Ähnliches wie den React Router erstellen. In Svelte v3 haben wir uns vom Verhalten von Webkomponenten entfernt und nie zurückgeschaut.


Leider war dies eines der grundlegenden Merkmale des DOM. Was uns zu ...


6. Verwirrung zwischen Eigenschaften und Attributen


Eigenschaften und Attribute sind im Grunde dasselbe, oder?


 const button = document.createElement('button'); button.hasAttribute('disabled'); // false button.disabled = true; button.hasAttribute('disabled'); // true button.removeAttribute('disabled'); button.disabled; // false 

Na ja, fast:


 typeof button.disabled; // 'boolean' typeof button.getAttribute('disabled'); // 'object' button.disabled = true; typeof button.getAttribute('disabled'); // 'string' 

Es gibt Namen, die nicht übereinstimmen:


 div = document.createElement('div'); div.setAttribute('class', 'one'); div.className; // 'one' div.className = 'two'; div.getAttribute('class'); // 'two' 

... und es gibt solche, die überhaupt nicht vereinbart sind:


 input = document.createElement('input'); input.getAttribute('value'); // null input.value = 'one'; input.getAttribute('value'); // null input.setAttribute('value', 'two'); input.value; // 'one' 

Aber wir könnten uns mit diesen Macken befassen, dem Zusammenspiel von String-Format (HTML) und DOM. Es gibt eine begrenzte Anzahl dieser Funktionen, sie sind dokumentiert, so dass wir zumindest etwas über sie lernen können, wenn wir Zeit und Geduld haben.


Webkomponenten machen einen Unterschied. Es gibt keine Garantie mehr für die Beziehung zwischen Eigenschaften und Attributen, und Sie als Webkomponentenentwickler müssen beide unterstützen. Was uns zu so etwas bringt:


 class MyThing extends HTMLElement { static get observedAttributes() { return ['foo', 'bar', 'baz']; } get foo() { return this.getAttribute('foo'); } set foo(value) { this.setAttribute('foo', value); } get bar() { return this.getAttribute('bar'); } set bar(value) { this.setAttribute('bar', value); } get baz() { return this.hasAttribute('baz'); } set baz(value) { if (value) { this.setAttribute('baz', ''); } else { this.removeAttribute('baz'); } } attributeChangedCallback(name, oldValue, newValue) { if (name === 'foo') { // ... } if (name === 'bar') { // ... } if (name === 'baz') { // ... } } } 

Sie können das Gegenteil tun - attributeChangedCallback Getter und Setter rufen auf. In jedem Fall ist die Bequemlichkeit, damit zu arbeiten, nur deprimierend. Gleichzeitig gibt es in Frameworks eine einfache und eindeutige Möglichkeit, Daten an eine Komponente zu übertragen.


7. Undichtes Design


Dieses Element ist etwas vage, aber es scheint mir seltsam, dass attributeChangedCallback nur eine Klassenmethode ist. Sie können buchstäblich Folgendes tun:


 const element = document.querySelector('my-thing'); element.attributeChangedCallback('w', 't', 'f'); 

Die Attribute haben sich nicht geändert, aber der Code verhält sich so, als ob es passiert wäre. Natürlich gab es in JavaScript immer viele Möglichkeiten, Schaden anzurichten, aber wenn ich sehe, dass ein Implementierungsdetail auf diese Weise herausragt, scheint mir, dass etwas mit dem Design eindeutig nicht stimmt.


8. Schlechtes DOM


Ok, wir haben bereits festgestellt, dass das DOM schlecht ist. Es ist jedoch immer noch schwer zu übertreiben, wie unpraktisch es ist, interaktive Anwendungen zu erstellen.


Vor einigen Monaten schrieb ich einen Artikel mit dem Titel „Schreibe weniger Code“, um zu veranschaulichen, wie Svelte Komponenten effizienter schreiben kann als Frameworks wie React und Vue. Es gab keinen Vergleich mit dem Vanille-DOM, sollte es aber. Kurz gesagt, wir haben eine einfache Komponente <Adder a={1} b={2}/> :


 <script> export let a; export let b; </script> <input type="number" bind:value={a}> <input type="number" bind:value={b}> <p>{a} + {b} = {a + b}</p> 

Das ist alles. Schreiben Sie nun dasselbe über die Webkomponente:


 class Adder extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); this.shadowRoot.innerHTML = ` <input type="number"> <input type="number"> <p></p> `; this.inputs = this.shadowRoot.querySelectorAll('input'); this.p = this.shadowRoot.querySelector('p'); this.update(); this.inputs[0].addEventListener('input', e => { this.a = +e.target.value; }); this.inputs[1].addEventListener('input', e => { this.b = +e.target.value; }); } static get observedAttributes() { return ['a', 'b']; } get a() { return +this.getAttribute('a'); } set a(value) { this.setAttribute('a', value); } get b() { return +this.getAttribute('b'); } set b(value) { this.setAttribute('b', value); } attributeChangedCallback() { this.update(); } update() { this.inputs[0].value = this.a; this.inputs[1].value = this.b; this.p.textContent = `${this.a} + ${this.b} = ${this.a + this.b}`; } } customElements.define('my-adder', Adder); 

Ja.


Beachten Sie, dass wir zwei separate Updates haben, wenn wir sowohl a als auch b synchron ändern. Die meisten Frameworks leiden nicht unter diesem Problem.


9. Globale Namen


Ich werde mich noch lange nicht darauf konzentrieren, es genügt zu sagen, dass die Gefahren der Arbeit in einem einzigen gemeinsamen Namespace seit langem bekannt und zerlegt sind.


10. Alle diese Probleme wurden bereits behoben.


Die größte Traurigkeit ist, dass wir bereits gute Komponentenmodelle haben. Wir lernen noch, aber die grundlegende Aufgabe - das Synchronisieren der Ansicht mit einem bestimmten Status durch Aktualisieren des DOM in einem komponentenorientierten Stil - wurde bereits vor einigen Jahren gelöst. Und wir fügen der Webplattform immer noch Funktionen hinzu, um das nachzuholen, was wir bereits in Bibliotheken und Frameworks haben.


Da unsere Ressourcen nicht unendlich sind, bedeutet die für eine Aufgabe aufgewendete Zeit einen Mangel an Aufmerksamkeit für eine andere Aufgabe. Trotz der allgemeinen Gleichgültigkeit der Entwickler wurde viel Energie für Webkomponenten aufgewendet. Was könnten wir erreichen, wenn wir diese Energie für etwas anderes ausgeben?

Source: https://habr.com/ru/post/de457010/


All Articles