Reaktives jQuery für spaghetti-verseuchte Legacy-Codebasen (oder wenn man keine schönen Dinge haben kann)

Avatar of Meredith Matthews
Meredith Matthews am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

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.