- 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
template → clone() → 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
Letztlich läuft es auf Web Components hinaus..
Glückwunsch. Noch ein weiteres JS-Framework ist geboren worden.
Hacker-News-Kommentare
Für viele JS-Entwickler ist das vielleicht Ketzerei, aber ich halte eine
state-Variable für ein Anti-Patternvalue/textContent/checkedder DOM-Elemente usw. als einzige Quelle der WahrheitIn der Dokumentation steht, dass dieser Ansatz sehr wartbar sei, aber ich stimme nicht zu
Ich schreibe in letzter Zeit Anwendungen in reinem „Vanilla“-TypeScript zusammen mit vite und hinterfrage dabei die „Best Practices“ im Frontend immer stärker
Dieser Ansatz erinnert mich an die alte Library backbone js
Ich habe mir vor Kurzem etwas Ähnliches ausgedacht, verwende aber keine Template-Elemente
innerHTMLbestehender Elemente ein oder füge sie in ein neu erzeugtesdiv-Element einDieser 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 verwende einen Helper ähnlich wie
React.createElementAls Versuch, ein JS-Toolkit für HTML-basierte Tools zu entwickeln, arbeite ich an deja-vu.junglecoder.com
In meinem ersten offiziellen Job nach dem Uni-Abschluss habe ich daran gearbeitet, eine Webversion von Delphi-Software zu erstellen