Ich höre dich jetzt schon schreien: „Warum zum Teufel sollte man jQuery verwenden, wenn es viel bessere Werkzeuge gibt? Wahnsinn! Was für ein Irrer bist du?“ Das sind berechtigte Fragen, und ich werde sie mit ein wenig Kontext beantworten.
In meinem aktuellen Job bin ich für die Pflege und Wartung einer Legacy-Website verantwortlich. Sie ist alt. Das Frontend basiert auf jQuery, und wie die meisten alten Legacy-Systeme ist sie nicht in bestem Zustand. Das allein ist nicht das Schlimmste, aber ich arbeite mit zusätzlichen Einschränkungen. Zum Beispiel arbeiten wir an einer kompletten Neufassung des Systems, daher werden massive Refactoring-Arbeiten nicht genehmigt, und ich darf auch keine neuen Abhängigkeiten zum bestehenden System hinzufügen, ohne eine vollständige Sicherheitsüberprüfung, die historisch bis zu einem Jahr dauern kann. Effektiverweise ist jQuery die *einzige* JavaScript-Bibliothek, die ich verwenden kann, da sie bereits vorhanden ist.
Mein Unternehmen hat erst kürzlich erkannt, dass Frontend-Entwickler wichtige Fähigkeiten beitragen könnten, daher wurde die gesamte Frontend-Seite der App von Entwicklern geschrieben, die von Best Practices nichts wussten und oft ihre Aufgabe verachteten. Infolgedessen ist die Codequalität insgesamt wild ungleichmäßig und ziemlich schlecht und unidiomatisch.
Ja, ich arbeite in *dieser* Legacy-Codebasis: prototypisches jQuery-Spaghetti.
Jemand muss es tun, und da es immer mehr Legacy-Code auf der Welt geben wird als Greenfield-Projekte, wird es immer viele von uns geben. Ich brauche auch kein Mitleid. Der Umgang damit, das Lernen, mit Frontend-Spaghetti in einem so riesigen Ausmaß umzugehen, hat mich zu einem besseren, wenn auch mürrischeren Entwickler gemacht.
Woher weißt du also, ob du Spaghetti-jQuery auf dem Tisch hast? Ein zuverlässiger Code-Geruch, den ich gefunden habe, ist das Fehlen des ehrwürdigen alten .toggle(). Wenn du es geschafft hast, eine Weile nicht mehr an jQuery zu denken, ist es eine Bibliothek, die Probleme mit der plattformübergreifenden Kompatibilität glättet und gleichzeitig DOM-Abfragen und -Mutationen unglaublich einfach macht. Daran ist nichts grundsätzlich falsch, aber direkte DOM-Manipulation kann sehr schwer zu skalieren sein, wenn man nicht vorsichtig ist. Je mehr DOM-Manipulation du schreibst, desto defensiver wirst du gegen DOM-Mutationen. Schließlich kannst du feststellen, dass die gesamte Codebasis auf diese Weise geschrieben ist, und in Kombination mit einem nicht idealen Scope-Management arbeitest du im Wesentlichen in einer App, bei der der gesamte Zustand im DOM liegt und du nie vertrauen kannst, in welchem Zustand das DOM sein wird, wenn du Änderungen vornehmen musst; Änderungen können von überall in deiner App hereinbrechen, ob du willst oder nicht. Dein Code wird prozeduraler, bläht sich mit mehr expliziten Anweisungen auf, versucht, alle benötigten Daten aus dem DOM selbst zu extrahieren und sie in den Zustand zu zwingen, in dem du sie haben musst.
Deshalb ist .toggle() oft das Erste, was wegfällt: Wenn du dir nicht sicher sein kannst, ob ein Element sichtbar ist oder nicht, musst du stattdessen .show() und .hide() verwenden. Ich sage nicht, dass .show() und .hide() als schädlich betrachtet werden sollten™, aber ich habe festgestellt, dass sie ein guter Hinweis darauf sind, dass es größere Probleme geben könnte.
Was kannst du dagegen tun? Eine Lösung, die meine Kollegen und ich gefunden haben, ist eine direkte Anlehnung an die reaktiven Frameworks, die wir lieber verwenden würden: Observables und State Management. Wir alle haben festgestellt, dass die manuelle Erstellung von State-Objekten und ereignisgesteuerten Update-Funktionen, **wobei wir unser DOM wie ein One-Way-Dataflow-Template behandeln**, zu **vorhersehbareren Ergebnissen** führt, die im Laufe der Zeit leichter zu ändern sind.
Jeder von uns nähert sich dem Problem ein wenig anders. Meine Herangehensweise an reaktives jQuery ist deutlich von Vue-Drop-in beeinflusst und nutzt einige „fortgeschrittene“ CSS-Features.
Wenn du dir das Skript ansiehst, wirst du sehen, dass zwei verschiedene Dinge passieren. Erstens haben wir ein State-Objekt, das alle Werte für unsere Seite enthält, und wir haben ein großes Durcheinander von Events.
var State = {
num: 0,
firstName: "",
lastName: "",
titleColor: "black",
updateState: function(key, value){
this[key] = value;
$("[data-text]").each(function(index, elem){
var tag = $(elem).attr("data-tag");
$(elem).text(State[tag]);
});
$("[data-color]").each(function(index, elem){
var tag = $(elem).attr("data-tag");
$(elem).attr("data-color", State[tag]);
});
}
};
Ich gebe es zu, ich liebe benutzerdefinierte HTML-Attribute und habe sie großzügig in meiner Lösung verwendet. Ich mochte nie, wie HTML-Klassen oft doppelte Aufgaben als CSS-Hooks und JavaScript-Hooks erfüllen und wie, wenn du eine Klasse für beide Zwecke gleichzeitig verwendest, du deinem Skript Brüchigkeit auferlegst. Dieses Problem verschwindet mit HTML-Attributen vollständig. Klassen werden wieder zu Klassen, und die Attribute werden zu Metadaten oder Styling-Hooks, die ich benötige.
Wenn du dir das HTML ansiehst, wirst du feststellen, dass jedes Element im DOM, das Daten anzeigen muss, ein data-tag-Attribut mit einem Wert hat, der einer Eigenschaft im State-Objekt entspricht, das die anzuzeigenden Daten enthält, und ein Attribut ohne Wert, das die Art der Transformation beschreibt, die auf das angewendete Element angewendet werden muss. Dieses Beispiel hat zwei verschiedene Arten von Transformationen: Text und Farbe.
<h1 data-tag="titleColor" data-color>jDux is super cool!</h1>
Nun zu den Events. Jede Änderung, die wir an unseren Daten vornehmen wollen, wird durch ein Event ausgelöst. Im Skript findest du jedes Event, um das wir uns kümmern, aufgelistet mit seiner eigenen .on()-Methode. Jedes Event löst eine Update-Methode aus und sendet zwei Informationen: welche Eigenschaft im State-Objekt aktualisiert werden muss und welchen neuen Wert sie erhalten soll.
$("#inc").on("click", function(){
State.updateState("num", State.num + 1)
});
$("#dec").on("click", function(){
State.updateState("num", State.num - 1)
});
$("#firstNameInput").on("input", function(){
State.updateState("firstName", $(this).val() )
});
$("#lastNameInput").on("input", function(){
State.updateState("lastName", $(this).val() )
});
$('[class^=button]').on("click", function(e) {
State.updateState('titleColor', e.target.innerText);
});
Das bringt uns zu State.updateState(), der Update-Funktion, die deine Seite mit deinem State-Objekt synchron hält. Jedes Mal, wenn sie ausgeführt wird, aktualisiert sie alle markierten Werte auf der Seite. Es ist nicht die effizienteste Methode, alles auf der Seite jedes Mal neu zu machen, aber es ist viel einfacher, und wie ich hoffe, habe ich bereits deutlich gemacht, ist dies eine unvollkommene Lösung für eine unvollkommene Codebasis.
$(document).ready(function(){
State.updateState();
});
Das Erste, was die Update-Funktion tut, ist, den Wert gemäß der erhaltenen Eigenschaft zu aktualisieren. Dann führt sie die beiden erwähnten Transformationen durch. Bei Textelementen sammelt sie alle data-text-Knoten, holt sich deren data-tag-Wert und setzt den Text auf alles, was in der markierten Eigenschaft steht. Farbe funktioniert etwas anders: Sie setzt das data-color-Attribut auf den Wert der markierten Eigenschaft und verlässt sich dann auf das CSS, das die data-color-Eigenschaften stylt, um den korrekten Stil anzuzeigen.
Ich habe auch document.ready hinzugefügt, damit wir die Update-Funktion beim Laden ausführen und unsere Standardwerte anzeigen können. Du kannst Standardwerte aus dem DOM oder einem AJAX-Aufruf abrufen oder das State-Objekt einfach mit bereits eingegebenen Werten laden, wie ich es hier getan habe.
Und das ist alles! Wir halten einfach den Zustand im JavaScript, beobachten unsere Events und reagieren auf Änderungen, wie sie passieren. Einfach, oder?
Was ist hier der Vorteil? Die Arbeit mit einem solchen Muster behält eine einzige Wahrheitsquelle in deinem State-Objekt, das du kontrollierst, vertrauen und durchsetzen kannst. Wenn du jemals das Vertrauen verlierst, dass dein DOM korrekt ist, musst du nur die Update-Funktion ohne Argumente erneut ausführen, und deine Werte sind wieder konsistent mit dem State-Objekt.
Ist das eine Art Heuchelei und primitiv? Absolut. Würdest du ein ganzes System damit aufbauen wollen? Sicherlich nicht. Wenn du bessere Werkzeuge zur Verfügung hast, solltest du sie benutzen. Aber wenn du in einer stark eingeschränkten Legacy-Codebasis wie ich bist, versuche, dein nächstes Feature mit reaktivem jQuery zu schreiben und sieh, ob es deinen Code und dein Leben einfacher macht.
Fantastischer Artikel und etwas, das ich sofort anwenden kann. Ich habe eine spaghetti-verseuchte Shopify-Website-Codebasis geerbt, und alles, womit wir arbeiten können, ist jQuery.
Nachdem ich einige Jahre mit React gearbeitet habe, fühlt es sich wie Magie an, zu jQuery zurückzukehren, aber man stößt wie du elegant erklärst, nur allzu oft auf die Probleme mit Zustand und Wahrheitsquelle.
Ich werde diese Methoden definitiv beim nächsten Refactoring ausprobieren.
Ich schätze, dass dies die Vorteile von State mit sich bringt, aber diese updateState-Funktion wird die Häufigkeit erhöhen, mit der jQuery das DOM durchsuchen muss, verglichen mit dem Schreiben von altmodischem Ninja-Code.
Der Grund, warum ich das sage, ist, dass ich mit einigen großen Legacy-Projekten arbeite und wenn der gesamte document.ready-Code refaktoriert und jedes Mal ausgeführt würde, wenn der Benutzer ein Ereignis auslöst, würde die Website zum Stillstand kommen.
Toller Artikel. Ich liebe es, dass du das alte Pasta-Geflecht „reaktifizieren“ konntest.
Nee, Mann.
Was du tun musst, ist, jQuery auszulagern – wenn du mehr Abhängigkeiten davon schaffst, wirst du es nie los.
„Keine Abhängigkeiten“, okay, aber du darfst offensichtlich neuen Code hinzufügen?
Also füge keine „Abhängigkeiten“ hinzu, sondern benutze eine Engine, die klein genug ist, um sie zu besitzen und zu kopieren/einzufügen.
Zum Beispiel ist dot-dom im Grunde React in 512 Bytes – zustandsbehaftete Komponenten mit Lebenszyklusereignissen und virtuellem DOM.
https://github.com/wavesoft/dot-dom
Es hat ziemlich moderne Browseranforderungen, aber es ist einfach, es mit Babel und ein paar schnellen Änderungen herunterzustufen.
Alternativ gibt es Superfine, das nur ein einfaches virtuelles DOM in etwa 300 LOC ist.
https://github.com/jorgebucaran/superfine
Keines davon hat Potenzial für Sicherheitsprobleme, aber sie ermöglichen es dir, dich schrittweise modernen UI-Mustern anzunähern, und sie müssen keine „Abhängigkeiten“ sein.
Rasmus, was du nicht siehst, ist, dass wenn ich sage „keine Abhängigkeiten“, das wirklich ernst meine. *Node ist eine Abhängigkeit, und wir dürfen es nicht installieren*. Es geht nicht um die Größe, es geht um Freiheit.
@merid, wenn du wirklich „keine Abhängigkeiten“ meinst, dann kannst du gar keinen Code schreiben, denn die Seite wird von dem Code abhängen, den du geschrieben hast, genauso wie du von dem bereits vorhandenen Code abhängst, den du umgehen musst. Wenn du zum Beispiel React Zeichen für Zeichen neu schreibst, ist das dann eine Abhängigkeit oder dein Code?
Dude, wenn ich das könnte und es abwärtskompatibel zu IE9 machen könnte, würde ich hier nicht arbeiten :)
Ich verstehe die Empörung gegen jQuery nicht. Seine historische Bedeutung für das Web als vereinheitlichende API über Webbrowser hinweg, die unfähig (und immer noch unfähig) waren, Standards zu befolgen und eine nahtlose plattformübergreifende Erfahrung zu ermöglichen, ist nicht zu unterschätzen. Warum, es hat wahrscheinlich „Web 2.0“ im Alleingang eingeleitet.
Und ehrlich gesagt, die moderne Kritik an seiner Größe angesichts der schwellenden und aufgeblähten Natur von JS-Bundles? Topf und Kessel lassen grüßen.
Für kleine Projekte ist es völlig in Ordnung, ein CDN-basiertes jQuery einzubinden und loszulegen. Das Einzige, was dies jetzt fast überflüssig macht, ist Svelte.
Ich liebe alles daran. Kreativ *und* pragmatisch. Prost an alle Entwickler da draußen, die die älteren Teile des Webs für uns am Laufen halten. :)
In neueren Versionen von jQuery kannst du einfach
data()verwenden, um direkt auf die Datenattribute zuzugreifen. Du musst nicht mehr getAttribute() dafür verwenden.Das solltest du in diesem Beispiel nicht tun, und meiner Meinung nach nie. .data() lädt Werte nur, wenn die Seite geladen wird, und speichert sie im Cache. Wenn du irgendwelche data-*-Attribute in deinem Code mutierst, werden deine JavaScript-Änderungen diese Updates nicht sehen.
https://api.jquery.com/data/#data-html5
Die Verwendung von data() und [data] sind wirklich zwei verschiedene Dinge mit einer kleinen Verbindung. Ich verstehe, warum du data() in diesem Projekt nicht verwenden kannst, aber für Leute, die das Glück haben, die volle Kontrolle über den Code zu haben und keine andere Bibliothek verwenden, hat data() definitiv seinen Platz.
.. Baue eine WebGL-App als Prototyp, bei der der Zustand das DOM ist .. kein Schatten- oder Spiegeldomein ..hole und setze direkt aus dem DOM. Hauptsächlich, weil ich mehrere Bibliotheken plus jQuery und Vanilla JS ziemlich beliebig mische ..manchmal in der gleichen Funktion !! ..schön zu wissen, dass ich mich an dich wenden kann, wenn es Zeit ist, alles auszumisten :)
Großartige Arbeit.
Ich bin ziemlich verwirrt, warum das so kontrovers ist.
Ich habe früher einige Dinge an einer SharePoint 2010 Intranet-Website gemacht (die sie immer noch verwenden), und dieses Muster wäre damals wirklich nützlich gewesen.
Das Einzige, was ich anders machen würde, wäre, ein Element nur dann zu aktualisieren, wenn sich der Wert tatsächlich geändert hat. z.B.
if ($(elem).text() != State[tag]) { $(elem).text(State[tag]) }Hey Bro, ich habe diesen Artikel geliebt! Ich habe mich sogar inspirieren lassen, dein Beispiel zu erweitern und eine etwas allgemeinere Lösung zu entwickeln! :D
https://jsfiddle.net/jeyssonguevara/L7gmqsty/2/
Der Name ‚unspaguetti‘ ist nur zum Spaß da, er sollte für etwas Kürzeres geändert werden XD
Hier ist ein kleines Beispiel, was dieses Experiment leisten kann.
Ich weiß, dass du nur Beispiele gibst, aber es könnte sich lohnen, die Selektoren im updateState-Funktion zu cachen. Ich kann mir nicht vorstellen, dass es eine gute Idee ist, jedes Mal das DOM nach allem zu durchsuchen, wenn der Benutzer ein beliebiges Ereignis auslöst.
Gut gemacht! Ich gebe zu, ich bin fast begeistert, etwas jQuery zu refaktorisieren.
Erstaunlich, wie eine Änderung der Perspektive unerwünschte Umstände verbessern kann (eine allgemeinere Lebenslektion für alle? Vielleicht)
Es scheint ein ziemlich kontroverses Thema zu sein. Ich verstehe die Empörung über diesen Weg nicht ganz. Es klang so, als ob es daran lag, dass man dem DOM-Status nicht trauen kann … aber dann hast du Code geschrieben, der aus dem DOM liest. Entweder kannst du ihm also vertrauen oder nicht. Es scheint, als ob du einfach daran gewöhnt bist, die Dinge auf die React-Art zu machen und so weiterarbeiten möchtest. Dagegen ist nichts grundsätzlich falsch. Denk nur daran, dass jeder, der dieses Projekt als nächstes übernimmt, sich mit dem ursprünglichen „spaghetti-verseuchten“ Code und deinem „reaktifizierten“ Code auseinandersetzen muss und wahrscheinlich in einer noch schlimmeren Situation ist als du.
Wenn der Legacy-Code wirklich so schlecht ist, wie du ihn darstellst, gibt es ein potenzielles Problem damit, wie dies eingerichtet ist. Du aktualisierst dein State-Objekt nur, wenn Events ausgelöst werden. Es besteht die Möglichkeit, dass anderer Code die Werte ändert, ohne Events auszulösen. Also bist du wieder in der Situation, etwas zu haben, dem du nicht als evangelisches Wort vertrauen kannst. Wenn du die updateState()-Funktion kurz bevor du mit dem Wert arbeiten musst, ausführst, könntest du genauso gut direkt aus dem DOM lesen und bräuchtest keine Event-Listener, die updateState() ausführen.
Oder vielleicht fehlt mir hier etwas völlig?
Der hier gezeigte Ansatz geht davon aus, dass dein DOM unzuverlässig ist, aber dein State-Objekt zuverlässig. Das Einzige, was dieser Code aus dem DOM zieht, sind Templating-Tags, weil du *etwas* brauchst, um dich daran zu hängen, aber ansonsten leben alle Daten im State-Objekt und werden in die Ansicht ausgegeben. Wenn deine Attribute oder dein State-Objekt remote mutiert werden, funktioniert dieser Ansatz offensichtlich nicht, und du hast größere Probleme, als ein Muster wie dieses bewältigen kann.
Warum keine Web Components verwenden?
Sie werden ziemlich gut unterstützt, also keine Abhängigkeit mehr und dein Code wird gut organisiert sein.
Nun, alles ist gut, wenn man eine jQuery-basierte Anwendung durch React, Vue, Angular etc. ersetzen will. Ich bin dabei. Aber! Mach es nicht bei bestehenden jQuery-Projekten. Meistens ist es nicht nur jQuery, es gibt immer jQuery-Plugins, die verwendet werden. Plugins wie datatables.js zum Aktivieren von Tabellen-Lookup/Filterung/Bearbeitung, Dropdown-Plugins, Datumsauswahl-Plugins, Diagramm-Plugins, DOM-Manipulations-Plugins usw. usw. Das erhöht den Arbeitsaufwand für die Konvertierung eines bestehenden jQuery-Projekts. Ganz zu schweigen von den Daten vom Backend, was ein weiteres „Problem“ ist, und je nachdem, welches Backend verwendet wird, wird die Konvertierung einfach oder schwierig. Ehe man sich versieht, ist man 10 Monate weiter an etwas, das eine „einfache“ Konvertierung sein sollte, und eine Konvertierung, die verdammt viel mehr kostet als geschätzt.
Es gibt wirklich keinen Grund, sich zu schämen, ein aktuelles Projekt zu pflegen, das auf jQuery basiert. Wenn du ein bestehendes jQuery-Projekt „konvertieren“ musst, ist die einfachste Methode, Projekt „MK2“ zu starten und das gewünschte Framework von Grund auf zu verwenden.
In sechs Jahren gibt es ein neues, besseres Framework namens „FizleJuice“, und dann solltest du das „jetzige Framework“ durch FizleJuice ersetzen und … hey Déjà-vu!
;)
Hallo,
Das Beste… Danke für das Teilen dieses Inhalts, als Entwickler ist es für mich wirklich hilfreich.