Lassen Sie uns etwas tun (und brechen)

Betrachten Sie eine einfache Anwendung, eine wirklich einfache. Zum Beispiel eine kleine App, um unsere Lieblingsorte auf einer Karte zu markieren. Nichts Besonderes dort: nur eine Kartenansicht rechts und eine einfache Seitenleiste links. Wenn Sie auf eine Karte klicken, wird eine neue Markierung auf einer Karte gespeichert.

Natürlich sind wir ein wenig ehrgeizig und wir werden zusätzliche Funktion zu machen: wir möchten, dass es sich an die Liste der Orte erinnert, an denen lokaler Speicher verwendet wird.

Basierend auf dieser Beschreibung können wir nun ein grundlegendes Aktionsflussdiagramm für unsere App zeichnen:

Wie Sie sehen, wird es nicht kompliziert.

Der Kürze halber werden die folgenden Beispiele ohne Verwendung eines Frameworks oder einer UI—Bibliothek geschrieben – nur Vanille-JavaScript ist beteiligt. Wenn Sie eine ähnliche App selbst erstellen möchten, sollten Sie Ihren API-Schlüssel unter https://cloud.google.com/maps-platform/#get-started registrieren.

Okay, kommen wir zur Codierung und erstellen einen schnellen Prototyp:

Lassen Sie uns dieses schnell analysieren:

  • init() die Funktion initialisiert das Kartenelement mithilfe der Google Maps-API, richtet die Kartenklickaktion ein und versucht dann, Markierungen aus dem lokalen Speicher zu laden.
  • addPlace() behandelt den Kartenklick – fügt dann der Liste einen neuen Ort hinzu und ruft das Marker-Rendering auf.
  • renderMarkers() durchläuft die Stellen im Array und setzt nach dem Löschen der Karte die Markierungen darauf.

Lassen Sie uns hier einige Unvollkommenheiten beiseite legen (Affen-Patching, keine Fehlerbehandlung usw.) – es wird gut genug als Prototyp dienen. Ordentlich. Lassen Sie uns dafür ein Markup erstellen:

Angenommen, wir haben einige Stile angehängt (wir werden es hier nicht behandeln, da es nicht relevant ist), glauben Sie mir oder nicht — es macht tatsächlich seinen Job:

Obwohl es hässlich ist, funktioniert es. Ist aber nicht skalierbar. Uh-oh.

Zuallererst mischen wir hier Verantwortlichkeiten. Wenn Sie von SOLIDEN Prinzipien gehört haben, sollten Sie bereits wissen, dass wir bereits die ersten brechen: Single Responsibility Prinzip. In unserem Beispiel kümmert sich — trotz seiner Einfachheit — eine Codedatei sowohl um die Handhabung der Benutzeraktionen als auch um den Umgang mit Daten und deren Synchronisation. Warum nicht? „Hey, es funktioniert, oder?“ – könnte man sagen. Nun, das tut es, aber es ist in Bezug auf die nächsten Funktionen kaum wartbar.

Lass mich dich auf andere Weise überzeugen. Stellen Sie sich vor, wir werden unsere App erweitern und dort neue Funktionen hinzufügen:

Zunächst möchten wir eine Liste der markierten Stellen in der Seitenleiste haben. Zweitens möchten wir Städtenamen per Google API nachschlagen – und hier werden asynchrone Mechanismen eingeführt.

Okay, unser neues Flussdiagramm wird folgende Form haben:

Hinweis: Die Suche nach Städtenamen ist keine Raketenwissenschaft, Google Maps bietet dafür eine wirklich einfache API. Sie können es selbst ausprobieren!

Es gibt eine bestimmte Eigenschaft, den Namen der Stadt von der Google-API abzurufen: Es ist nicht sofort. Es muss den richtigen Dienst in der JavaScript-Bibliothek von Google aufrufen, und es wird einige Zeit dauern, bis die Antwort zurückkommt. Es wird eine kleine Komplikation verursachen — aber sicherlich eine pädagogische.

Kehren wir zur Benutzeroberfläche zurück und versuchen, eine offensichtliche Sache zu bemerken. Es gibt zwei angeblich separate Oberflächenbereiche: die Seitenleiste und den Hauptinhaltsbereich. Wir sollten definitiv keinen großen Code schreiben, der beides behandelt. Der Grund liegt auf der Hand — was ist, wenn wir in Zukunft vier Komponenten haben werden? Oder sechs? Oder 100? Wir müssen unseren Code in Stücke teilen — auf diese Weise haben wir zwei separate JavaScript-Dateien. Eine für die Seitenleiste, die zweite für die Karte. Und die Frage ist — welche sollte das Array mit den Orten halten?

Welcher Ansatz ist hier richtig? Nummer eins oder Nummer zwei? Nun, die Antwort lautet: keiner von beiden. Erinnern Sie sich an das Single Responsibility Prinzip? Um sauber und modular (und cool) zu bleiben, sollten wir die Bedenken irgendwie trennen und unsere Datenlogik woanders aufbewahren. Siehe:

Heiliger Gral der Codetrennung: Wir können den Speicher und die Logik in eine andere Codedatei verschieben, die sich zentral mit den Daten befasst. Diese Datei – der Dienst – ist für diese Bedenken und Mechanismen wie die Synchronisation mit dem lokalen Speicher verantwortlich. Im Gegensatz dazu dienen Komponenten nur als Schnittstellenteile. SOLIDE wie es sein sollte. Versuchen wir, dieses Muster einzuführen:

Servicecode:

Komponentencode zuordnen:

Code der Sidebar-Komponente:

Nun, ein großer Teil des Juckreizes ist weg. Der Code ist wieder ordentlich in den richtigen Schubladen organisiert. Aber bevor wir uns wieder gut fühlen, lassen Sie uns diesen ausführen.

…hoppla.
Nach einer Aktion reagiert die Schnittstelle nicht.

Warum? Nun, wir haben keine Synchronisationsmittel implementiert. Nachdem wir einen Ort mit der importierten Methode hinzugefügt haben, werden wir nirgendwo darüber informiert. Wir können die getPlaces() -Methode nach dem Aufruf von addPlace() nicht einmal in die nächste Zeile einfügen, da die Suche asynchron ist und einige Zeit in Anspruch nimmt.

Dinge passieren im Hintergrund, aber unsere Benutzeroberfläche ist sich ihrer Ergebnisse nicht bewusst – nachdem wir eine Markierung auf der Karte hinzugefügt haben, sehen wir die Änderung nicht in der Seitenleiste. Wie es zu lösen?

Eine ziemlich einfache Idee ist es, unseren Service ab und zu abzufragen — zum Beispiel könnte jede Komponente jede Sekunde Elemente vom Service erhalten. Z.B.:

Funktioniert es? Abgehackt, aber ja. Aber ist es optimal? Überhaupt nicht – wir überfluten die Ereignisschleife unserer App mit Aktionen, die in den meisten Fällen keine Auswirkungen haben.

Schließlich besuchen Sie nicht jede Stunde Ihr lokales Postamt, um zu überprüfen, ob das Paket angekommen ist. Wenn Sie unser Auto zur Reparatur verlassen, rufen Sie den Mechaniker nicht jede halbe Stunde an, um zu fragen, ob die Arbeit erledigt ist (zumindest hoffentlich sind Sie nicht diese Art von Person). Stattdessen warten wir auf einen Anruf. Und wenn der Job fertig ist, woher weiß der Mechaniker, wen er anrufen soll? Albern — natürlich haben wir ihm unsere Kontaktdaten hinterlassen.

Versuchen wir nun, das „Verlassen unserer Kontaktdaten“ im JavaScript nachzuahmen.