Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Web Development

Erstellen großer, wartbarer und testbarer Knockout.js-Anwendungen

by
Read Time:12 minsLanguages:

German (Deutsch) translation by Katharina Grigorovich-Nevolina (you can also view the original English article)

Knockout.js ist ein beliebtes Open Source (MIT) MVVM-JavaScript-Framework, das von Steve Sandersen erstellt wurde. Die Website bietet großartige Informationen und Demos zum Erstellen einfacher Anwendungen, bei größeren Anwendungen jedoch leider nicht. Lassen Sie uns einige dieser Lücken füllen!


AMD und Require.js

AMD ist ein JavaScript-Modulformat und eines der beliebtesten (wenn nicht die meisten) Frameworks ist http://requirejs.org von https://twitter.com/jrburke. Es besteht aus zwei globalen Funktionen namens require() und define(), obwohl require.js auch eine JavaScript-Startdatei enthält, z. B. main.js.

Es gibt hauptsächlich zwei Varianten von require.js: eine Vanilla require.js-Datei und eine, die jQuery (require-jquery) enthält. Letzteres wird natürlich überwiegend in jQuery-fähigen Websites verwendet. Nachdem Sie Ihrer Seite eine dieser Dateien hinzugefügt haben, können Sie der Datei main.js den folgenden Code hinzufügen:

Die Funktion require() wird normalerweise in der Datei main.js verwendet. Sie können sie jedoch verwenden, um ein Modul an einer beliebigen Stelle direkt einzuschließen. Es werden zwei Argumente akzeptiert: eine Liste von Abhängigkeiten und eine Rückruffunktion.

Die Rückruffunktion wird ausgeführt, wenn alle Abhängigkeiten vollständig geladen sind, und die an die Rückruffunktion übergebenen Argumente sind die Objekte, die in dem oben genannten Array erforderlich sind.

Es ist wichtig zu beachten, dass die Abhängigkeiten asynchron geladen werden. Nicht alle Bibliotheken sind AMD-kompatibel, aber require.js bietet einen Mechanismus zum Shimben dieser Bibliothekstypen, damit sie geladen werden können.

Für diesen Code ist ein Modul namens app erforderlich, das wie folgt aussehen kann:

Der Zweck der Funktion define() besteht darin, ein Modul zu definieren. Es werden drei Argumente akzeptiert: der Name des Moduls (das normalerweise nicht enthalten ist), eine Liste der Abhängigkeiten und eine Rückruffunktion. Mit der Funktion define() können Sie eine Anwendung in viele Module aufteilen, die jeweils eine bestimmte Funktion haben. Das fördert die Entkopplung und Trennung von Bedenken, da jedes Modul seine eigenen spezifischen Verantwortlichkeiten hat.

Knockout.js und Require.js zusammen verwenden

Knockout ist AMD-fähig und definiert sich als anonymes Modul. Sie müssen es nicht shimen; Nehmen Sie es einfach in Ihre Pfade auf. Die meisten AMD-fähigen Knockout-Plugins listen es als "Knockout" und nicht als "ko" auf, aber Sie können einen der folgenden Werte verwenden:

Dieser Code befindet sich oben in main.js. Die Option path definiert eine Zuordnung gängiger Module, die mit einem Schlüsselnamen geladen werden, anstatt den gesamten Dateinamen zu verwenden.

Die shim-Option verwendet einen in paths definierten Schlüssel und kann zwei spezielle Schlüssel haben, die als exports und deps bezeichnet werden. Der exports-Schlüssel definiert, was das Shimmed-Modul zurückgibt, und deps definiert andere Module, von denen das Shimmed-Modul abhängig sein kann. Beispielsweise könnte der Shim von jQuery Validate wie folgt aussehen:

Single- vs Multi-Page-Apps

Es ist üblich, alle erforderlichen JavaScript-Dateien in eine einzelne Seitenanwendung aufzunehmen. So können Sie die Konfiguration und die anfänglichen Anforderungen einer einseitigen Anwendung in main.js wie folgt definieren:

Möglicherweise benötigen Sie auch separate Seiten, die nicht nur seitenspezifische Module enthalten, sondern auch einen gemeinsamen Satz von Modulen verwenden. James Burke verfügt über zwei Repositorys, die diese Art von Verhalten implementieren.

Im Rest dieses Artikels wird davon ausgegangen, dass Sie eine mehrseitige Anwendung erstellen. Ich werde main.js in common.js umbenennen und die erforderliche require.config im obigen Beispiel in die Datei aufnehmen. Das ist rein semantisch.

Jetzt benötige ich common.js in meinen Dateien wie folgt:

Die Funktion require.config wird ausgeführt und erfordert die Hauptdatei für die jeweilige Seite. Die pages/index-Hauptdatei sieht möglicherweise folgendermaßen aus:

Dieses page/index-Modul ist jetzt für das Laden des gesamten erforderlichen Codes für die Seite index.html verantwortlich. Sie können dem Seitenverzeichnis weitere Hauptdateien hinzufügen, die auch für das Laden der abhängigen Module verantwortlich sind. Auf diese Weise können Sie mehrseitige Apps in kleinere Teile zerlegen und unnötige Skripteinschlüsse vermeiden (z. B. das JavaScript für index.html in die about.html-Seite aufnehmen).


Beispielanwendung

Schreiben wir eine Beispielanwendung mit diesem Ansatz. Es wird eine durchsuchbare Liste von Biermarken angezeigt und wir können Ihre Favoriten auswählen, indem wir auf deren Namen klicken. Hier ist die Ordnerstruktur der App:

"Folder structure"

Schauen wir uns zunächst das HTML-Markup von index.html an:

Seiten

Die Struktur unserer Anwendung verwendet mehrere "Seiten" oder "Netz" in einem pages-Verzeichnis. Diese separaten Seiten sind für die Initialisierung jeder Seite in der Anwendung verantwortlich.

Die ViewModels sind für das Einrichten der Knockout-Bindungen verantwortlich.

ViewModels

Im ViewModels-Ordner befindet sich die Hauptanwendungslogik von Knockout.j. Das IndexViewModel sieht beispielsweise folgendermaßen aus:

Das IndexViewModel definiert einige grundlegende Abhängigkeiten am Anfang der Datei und erbt BaseViewModel, um seine Mitglieder als beobachtbare Objekte von knockout.j zu initialisieren (wir werden dies in Kürze diskutieren).

Anstatt alle verschiedenen ViewModel-Funktionen als Instanzmitglieder zu definieren, erweitert die Funktion extens() von underscore.js den prototype des Datentyps IndexViewModel.

Vererbung und ein BaseModel

Vererbung ist eine Form der Wiederverwendung von Code, mit der Sie Funktionen zwischen ähnlichen Objekttypen wiederverwenden können, anstatt diese Funktionen neu zu schreiben. Daher ist es nützlich, ein Basismodell zu definieren, das andere Modelle erben oder von denen sie erben können. In unserem Fall ist unser Basismodell BaseViewModel:

Der BaseViewModel-Typ definiert zwei Methoden für seinen prototype. Das erste ist initialize(), das in den Untertypen überschrieben werden sollte. Das zweite ist _setup(), das das Objekt für die Datenbindung einrichtet.

Die _setup-Methode durchläuft die Eigenschaften des Objekts. Wenn die Eigenschaft ein Array ist, wird die Eigenschaft als ObservableArray festgelegt. Alles andere als ein Array wird observablegemacht. Außerdem wird nach den Anfangswerten der Eigenschaften gesucht und diese bei Bedarf als Standardwerte verwendet. Dies ist eine kleine Abstraktion, bei der die observable und observableArray-Funktionen nicht ständig wiederholt werden müssen.

Das "this" Problem

Personen, die Knockout verwenden, bevorzugen Instanzmitglieder gegenüber Prototypmitgliedern, da Probleme bei der Aufrechterhaltung des richtigen Werts von this auftreten. Das Schlüsselwort this ist eine komplizierte Funktion von JavaScript, aber es ist nicht so schlimm, wenn es erst einmal vollständig bearbeitet wurde.

Aus dem MDN:

"Im Allgemeinen wird this im aktuellen Bereich daran gebundene Objekt davon bestimmt, wie die aktuelle Funktion aufgerufen wurde. Es kann während der Ausführung nicht durch Zuweisung festgelegt werden und kann bei jedem Aufruf der Funktion unterschiedlich sein."

Der Umfang ändert sich also abhängig davon, wie eine Funktion aufgerufen wird. Das wird in jQuery deutlich:

Dieser Code richtet einen einfachen click-Ereignishandler für ein Element ein. Der Rückruf ist eine anonyme Funktion und führt nichts aus, bis jemand auf das Element klickt. In diesem Fall bezieht sich der Umfang von this dieser Funktion auf das eigentliche DOM-Element. Beachten Sie vor diesem Hintergrund das folgende Beispiel:

Hier gibt es ein Problem. Die in mySuperButtonClicked() verwendete this.someVariable gibt undefined zurück, da sich this im Rückruf eher auf das DOM-Element als auf das someCallbacks-Objekt bezieht.

Es gibt zwei Möglichkeiten, um dieses Problem zu vermeiden. Der erste verwendet eine anonyme Funktion als Ereignishandler, die wiederum someCallbacks.mySuperButtonClicked() aufruft:

Die zweite Lösung verwendet entweder die Methoden Function.bind() oder _.bind() (Function.bind() ist in älteren Browsern nicht verfügbar). Beispielsweise:

Jede von Ihnen gewählte Lösung erzielt das gleiche Endergebnis: mySuperButtonClicked() wird im Kontext someCallbacks ausgeführt.

"this" in Bindungen und Unit-Tests

In Bezug auf Knockout kann sich this Problem bei der Arbeit mit Bindungen zeigen - insbesondere beim Umgang mit $root und $parent. Ryan Niemeyer hat ein Plugin für delegierte Ereignisse geschrieben, das dieses Problem größtenteils beseitigt. Es gibt Ihnen mehrere Optionen zum Festlegen von Funktionen, aber Sie können das data-click-Attribut verwenden, und das Plugin führt Ihre Bereichskette durch und ruft die Funktion mit der richtigen this auf.

In diesem Beispiel wird $parent.addToFavorites über eine click-Bindung an das Ansichtsmodell gebunden. Da sich das <li /> Element in einer foreach-Bindung befindet, bezieht sich this in $parent.addToFavorites auf eine Instanz eines Bieres, auf das geklickt wurde.

Um dies zu umgehen, stellt die _.bindAll-Methode sicher, dass this ihren Wert beibehält. Daher wird das Problem behoben, indem der initialize()-Methode Folgendes hinzugefügt wird:

Die Methode _.bindAll() erstellt im Wesentlichen ein Instanzelement mit dem Namen addToFavorites() für das IndexViewModel-Objekt. Dieses neue Mitglied enthält die Prototypversion von addToFavorites(), die an das IndexViewModel-Objekt gebunden ist.

Das this Problem ist, warum einige Funktionen, wie z. B. ko.computed(), ein optionales zweites Argument akzeptieren. Ein Beispiel finden Sie in Zeile fünf. Das this als zweites Argument übergebene Argument stellt sicher, dass this korrekt auf das aktuelle IndexViewModel-Objekt in filterBeers verweist.

Wie würden wir diesen Code testen? Schauen wir uns zunächst die Funktion addToFavorites() an:

Wenn wir das mocha-Test-Framework verwenden und expect.js für Aussagen, würde unser Unit-Test wie folgt aussehen:

Überprüfen Sie das Repository, um das vollständige Setup für Unit-Tests anzuzeigen.

Testen wir nun filterBeers(). Schauen wir uns zunächst den Code an:

Diese Funktion verwendet die search()-Methode, die an den value eines text <input />-Elements im DOM gebunden ist. Anschließend wird das Dienstprogramm ko.utils.arrayFilter verwendet, um Übereinstimmungen aus der Bierliste zu suchen und zu finden. Der beerListFiltered ist an das <ul />-Element im Markup gebunden, sodass die Liste der Biere durch einfaches Eingeben in das Textfeld gefiltert werden kann.

Die filterBeers-Funktion, die eine so kleine Codeeinheit ist, kann ordnungsgemäß auf Einheiten getestet werden:

Erstens stellt dieser Test sicher, dass die BeerListFiltered tatsächlich eine Funktion ist. Anschließend wird eine Abfrage durchgeführt, indem der Wert von "bud" an this.viewModel.search() übergeben wird. Das sollte dazu führen, dass sich die Liste der Biere ändert und jedes Bier herausfiltert, das nicht zu "Knospe" passt. Anschließend wird search auf eine leere Zeichenfolge gesetzt, um sicherzustellen, dass beerListFiltered die vollständige Liste zurückgibt.


Schlussfolgerung

Knockout.js bietet viele großartige Funktionen. Beim Erstellen großer Anwendungen ist es hilfreich, viele der in diesem Artikel beschriebenen Prinzipien zu übernehmen, damit der Code Ihrer App verwaltbar, testbar und wartbar bleibt. Schauen Sie sich die vollständige Beispielanwendung an, die einige zusätzliche Themen wie messaging enthält. Es verwendet postal.js als Nachrichtenbus, um Nachrichten in der gesamten Anwendung zu übertragen. Die Verwendung von Messaging in einer JavaScript-Anwendung kann dazu beitragen, Teile der Anwendung zu entkoppeln, indem harte Verweise aufeinander entfernt werden. Seien Sie sicher und werfen Sie einen Blick darauf!

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.