10 Punkte von GN⁺ 2025-04-23 | 3 Kommentare | Auf WhatsApp teilen
  • Writing JavaScript Views the Hard Way: Ein Artikel, der erklärt, wie sich Views ohne Framework nur mit reinem JavaScript erstellen lassen
  • Durch einen direkten imperativen Ansatz werden Performance, Wartbarkeit und Portabilität erreicht
  • Status-Updates und DOM-Updates werden klar getrennt, und je nach Aufgabe werden strenge Benennungsregeln und strukturelle Muster befolgt
  • Dieser Ansatz ist leicht zu debuggen, garantiert Kompatibilität mit allen Browsern und hat den großen Vorteil von 0 dependencies
  • Für Einsteiger kann er schwierig sein, bietet beim Lernen aber ein tiefes Verständnis dafür, wie reale Systeme funktionieren

JavaScript-Views auf die harte Tour schreiben

Was ist das?

  • Dieser Ansatz ist ein Muster zum Bauen von Views nur mit JavaScript ohne Frameworks wie React, Vue, lit-html
  • Es handelt sich nicht um eine bestimmte Bibliothek oder ein Tool, sondern um ein Coding-Muster selbst, das Spaghetti-Code verhindert
  • Durch die Verwendung eines direkten imperativen Ansatzes werden Abstraktionen reduziert und die Verständlichkeit erhöht

Vorteile gegenüber Frameworks

  • Performance: Imperativer Code arbeitet ohne unnötige Berechnungen und eignet sich sowohl für Hot Paths als auch Cold Paths
  • 0 dependencies: Keine Probleme durch Library-Upgrades oder Kompatibilitätskonflikte
  • Portabilität: Geschriebener Code lässt sich in jedes Framework übertragen
  • Wartbarkeit: Durch klare Abschnittsstrukturen und Benennungsregeln lassen sich relevante Codestellen leicht finden
  • Browser-Support: Kompatibel mit den meisten Browsern ab IE9, mit einigen Anpassungen sogar bis IE6
  • Einfaches Debugging: Bietet flache Stack Traces ohne Zwischenschichten
  • Funktionale Struktur: Nicht unveränderlich, aber alle Bestandteile sind funktionsbasiert aufgebaut

Erklärung der Struktur

Gesamtstruktur

  • Besteht aus templateclone()init()
  • Die Funktion init() erzeugt eine View-Instanz, die Statusvariablen, DOM-Referenzen, Update-Funktionen, Event-Listener usw. enthält

Beispiel für die Codestruktur (Hello World)

const template = document.createElement('template');  
template.innerHTML = `<div>Hello <span id="name">world</span>!</div>`;  
  
function clone() {  
  return document.importNode(template.content, true);  
}  
  
function init() {  
  let frag = clone();  
  let nameNode = frag.querySelector('#name');  
  let name;  
  
  function setNameNode(value) {  
    nameNode.textContent = value;  
  }  
  
  function setName(value) {  
    if(name !== value) {  
      name = value;  
      setNameNode(value);  
    }  
  }  
  
  function update(data = {}) {  
    if(data.name) setName(data.name);  
    return frag;  
  }  
  
  return update;  
}  

Aufbau innerhalb der Funktion init()

1. DOM-Variablen

  • frag ist das durch clone() erzeugte Template-Fragment
  • Auf innere Elemente wird mit querySelector() zugegriffen, und Variablennamen folgen dem Muster fooNode

2. DOM-Views

  • Bereiche, die andere Views enthalten (wiederverwendbare Sub-Views)
  • Beispiel:
let updateChildView = childView();  
  • Update-Funktionen für Views werden nach dem Muster updateFoo benannt

3. Statusvariablen

  • Datenwerte, die sich innerhalb der View ändern können
  • Um DOM-Updates effizient zu halten, wird mit dem aktuellen Wert verglichen und das DOM nur bei Bedarf geändert

4. DOM-Update-Funktionen

  • Werden verwendet, um den Zustand von DOM-Elementen zu ändern
  • Beispiel:
function setNameNode(value) {  
  nameNode.textContent = value;  
}  
  • DOM-Manipulationen dürfen ausschließlich innerhalb dieser Funktionen erfolgen

5. Status-Update-Funktionen

  • Enthalten die Logik für Statusänderungen und die entsprechende Abbildung im DOM
  • Unveränderte Werte werden ignoriert, um unnötige DOM-Änderungen zu vermeiden
  • Beispiel:
function setName(value) {  
  if(name !== value) {  
    name = value;  
    setNameNode(value);  
  }  
}  

Die Funktionen template und clone()

template

  • Erstellt mit dem Element <template> eine statische HTML-Struktur
  • Wird nicht direkt in das DOM eingefügt, sondern per clone als Kopie erzeugt

clone()

  • Klont mit document.importNode(template.content, true)
  • Bei Bedarf kann mit .firstElementChild das Root-Element zurückgegeben werden

Interaktionsweise

Datenfluss von Eltern zu Kind

  • Das Elternelement ruft init() des Kindelements auf, um die Update-Funktion zu erhalten, und ruft sie dann in der Form update({ name: 'foo' }) auf

Ereignisbasierte Datenweitergabe

  • Folgt grundsätzlich dem Modell props down, events up
  • Untergeordnete Views kommunizieren, indem sie Events nach oben dispatchen

Vergleich mit React

  • constructor() (React)init() (Hard Way)
    • Zuständig für die anfängliche Einrichtung der Komponente
  • render() (React)update(data) (Hard Way)
    • Übernimmt das Aktualisieren der Anzeige und des UI
  • this.setState() (React)setX(value) (Hard Way)
    • Ersetzt durch einen Ansatz, bei dem Statuswerte direkt gesetzt werden
  • props (React)per update(data) übergebene Werte (Hard Way)
    • Art der Verarbeitung von Daten, die von der Elternkomponente übergeben werden
  • JSX / Virtual DOM (React)HTML-Template + DOM-API (Hard Way)
    • Statt deklarativer UI werden manuelle DOM-Manipulation und Templates verwendet

Fazit

  • Im Vergleich zu vertrauten Frameworks hat dieser Ansatz eine höhere Einstiegshürde, bietet aber folgende Stärken:
    • Performance-Optimierung
    • Vollständige Kontrolle
    • Tiefes Verständnis durch Lernen
  • Durch die Trennung der Funktionen nach Rollen sowie Benennungsregeln lässt sich auch ohne Framework eine wartbare UI aufbauen

Kompatibilität

  • Die aktuellen Beispiele verwenden APIs für moderne Browser, lassen sich aber über funktionsbasierte Alternativen auch bis IE9 und darunter unterstützen
  • Durch die Übergabe von Funktionen per props statt Events ist eine Erweiterung sogar bis IE6 möglich

3 Kommentare

 
wfedev 2025-04-24

Letztlich läuft es auf Web Components hinaus..

 
ahwjdekf 2025-04-23

Glückwunsch. Noch ein weiteres JS-Framework ist geboren worden.

 
GN⁺ 2025-04-23
Hacker-News-Kommentare
  • Für viele JS-Entwickler ist das vielleicht Ketzerei, aber ich halte eine state-Variable für ein Anti-Pattern

    • Bei der Verwendung von Web Components nutze ich statt zusätzlicher State-Variablen für „flache“ Variablentypen value/textContent/checked der DOM-Elemente usw. als einzige Quelle der Wahrheit
    • Falls nötig, füge ich Setter und Getter hinzu
    • Auch wenn es weniger Code ist, funktioniert auf natürliche Weise vieles korrekt
    • Mit WebComponents werden Objekte und die benachbarten HTML-Templates getrennt, sodass statt Spaghetti-Code eher eine Unterteilung wie Fusilli oder Makkaroni entsteht
  • In der Dokumentation steht, dass dieser Ansatz sehr wartbar sei, aber ich stimme nicht zu

    • Das Design-Pattern basiert nur auf Konventionen
    • Wenn in einer komplexen App mehrere Entwickler gleichzeitig arbeiten, ist es sehr wahrscheinlich, dass mindestens einer von den Konventionen abweicht
    • Klassenbasierte UI-Frameworks wie UIKit auf iOS zwingen alle Entwickler dazu, einen standardisierten Satz von APIs zu verwenden, wodurch der Code vorhersehbar und leichter wartbar wird
  • Ich schreibe in letzter Zeit Anwendungen in reinem „Vanilla“-TypeScript zusammen mit vite und hinterfrage dabei die „Best Practices“ im Frontend immer stärker

    • Zur Skalierbarkeit kann ich kein abschließendes Urteil fällen, aber in Bezug auf Performance gibt es große Vorteile
    • Es macht Spaß, ich lerne viel, das Debugging ist einfach, und die Architektur ist leicht zu verstehen
    • Am meisten vermisse ich Templating
  • Dieser Ansatz erinnert mich an die alte Library backbone js

    • Es gibt auch ein GitHub-Repository mit Beispielen für das an die Webplattform angepasste MVC-Pattern
  • Ich habe mir vor Kurzem etwas Ähnliches ausgedacht, verwende aber keine Template-Elemente

    • Ich nutze Funktionen und Template-Literale, um Strings zurückzugeben, und setze sie in das innerHTML bestehender Elemente ein oder füge sie in ein neu erzeugtes div-Element ein
    • Die Funktionen werden verschachtelt, wodurch sie sich nur schwer auf sinnvolle Weise strukturieren lassen
  • Dieser Code sieht exakt nach dem manuellen Update-Code aus, den reaktive View-Libraries eigentlich ersetzen wollen

  • Ich programmiere seit etwa 20 Jahren, aber mit Frontend-Frameworks bin ich nie warm geworden

    • Ich komme eher aus dem Backend und finde, dass sicherheitsrelevante Interaktionen über den Server laufen sollten
    • Ich sehe JS als Mittel, um auf einer soliden HTML- und CSS-Basis clientseitige Funktionen hinzuzufügen
  • Ich verwende einen Helper ähnlich wie React.createElement

    • Es gibt ein funktionsfähiges Beispiel für ein Mock-Server-Dashboard
  • Als Versuch, ein JS-Toolkit für HTML-basierte Tools zu entwickeln, arbeite ich an deja-vu.junglecoder.com

    • Eine vernünftige reaktive/zweiseitige Datenbindung habe ich noch nicht gelöst, aber grab/patch ist ziemlich ordentlich
    • Die Art, wie Templates verwendet werden, macht es sehr einfach, Teile eines Templates zu verschieben
  • In meinem ersten offiziellen Job nach dem Uni-Abschluss habe ich daran gearbeitet, eine Webversion von Delphi-Software zu erstellen

    • Das Team war bereits dabei, das Frontend zum dritten Mal neu zu schreiben, und wir mussten das Framework wechseln
    • Ich habe darauf bestanden, dass wir ein eigenes Framework schreiben sollten, aber das Team mochte meinen Vorschlag nicht
    • Später bin ich gegangen, nachdem ich von einem anderen Unternehmen ein besseres Angebot bekommen hatte
    • Danach habe ich noch ein weiteres Framework namens tiny.js ausprobiert und nutze es nun für private Projekte