Aufdecken von ClojureScript für die Front-End-Entwicklung
Veröffentlicht: 2022-03-11Während Sie diese Einführung lesen, gehen Ihnen wahrscheinlich zwei Hauptgedanken durch den Kopf:
- Was ist ClojureScript?
- Das ist nicht relevant für meine Interessen.
Aber warte! Da liegen Sie falsch – und ich werde es Ihnen beweisen. Wenn Sie bereit sind, 10 Minuten Ihrer Zeit zu investieren, zeige ich Ihnen, wie ClojureScript das Schreiben von Front-End- und React-y-Anwendungen unterhaltsam, schnell und vor allem funktional machen kann.
Einige ClojureScript-Tutorial-Voraussetzungen
- Lisp-Kenntnisse sind nicht erforderlich. Ich werde mein Bestes tun, um alle Codebeispiele zu erklären, die in diesem Blogbeitrag verstreut sind!
- Wenn Sie sich jedoch ein wenig vorlesen möchten, empfehle ich https://www.braveclojure.com/, die zentrale Anlaufstelle für den Einstieg in Clojure (und damit ClojureScript).
- Clojure und ClojureScript teilen eine gemeinsame Sprache – ich werde sie oft gleichzeitig mit Clojure[Script] bezeichnen.
- Ich gehe davon aus, dass Sie über React-Kenntnisse und allgemeines Frontend-Know-how verfügen.
So lernen Sie ClojureScript: Die Kurzversion
Sie haben also nicht viel Zeit, um ClojureScript zu lernen, und Sie möchten einfach sehen, wohin die ganze Sache führt. Das Wichtigste zuerst, was ist ClojureScript?
Von der ClojureScript-Website: ClojureScript ist ein Compiler für Clojure, der auf JavaScript abzielt. Es gibt JavaScript-Code aus, der mit dem erweiterten Kompilierungsmodus des Google Closure-Optimierungscompilers kompatibel ist.
ClojureScript hat unter anderem einiges zu bieten:
- Es ist eine Multiparadigmen-Programmiersprache mit einer funktionalen Programmiersprache – funktionale Programmierung ist dafür bekannt, die Lesbarkeit von Code zu verbessern und Ihnen dabei zu helfen , mit weniger Code mehr zu schreiben .
- Es unterstützt standardmäßig Unveränderlichkeit – verabschieden Sie sich von einer ganzen Reihe von Laufzeitproblemen!
- Es ist datenorientiert: Code ist Daten in ClojureScript. Die meisten Clojure[Script]-Anwendungen können auf eine Reihe von Funktionen reduziert werden, die auf einer zugrunde liegenden Datenstruktur arbeiten, was das Debuggen einfach und den Code super lesbar macht.
- Es ist einfach! Der Einstieg in ClojureScript ist einfach – es gibt keine ausgefallenen Schlüsselwörter und sehr wenig Zauberei.
- Es hat eine fantastische Standardbibliothek. Dieses Ding hat alles.
Lassen Sie uns diese Dose mit Würmern mit einem Beispiel öffnen:
(defn component [] [:div "Hello, world!"])
Hinweis für diejenigen, die mit Lisp-Dialekten oder ClojureScript nicht vertraut sind: Die wichtigsten Teile dieses Beispiels sind :div
, []
und ()
. :div
ist ein Schlüsselwort, das das Element <div>
darstellt. []
ist ein Vektor, ähnlich wie eine ArrayList
in Java, und ()
ist eine Sequenz, ähnlich wie eine LinkedList
. Darauf werde ich später in diesem Beitrag noch genauer eingehen!
Dies ist die grundlegendste Form einer React-Komponente in ClojureScript. Das war's – nur ein Schlüsselwort, eine Zeichenfolge und eine ganze Reihe von Listen.
Pah! Sie sagen, das unterscheidet sich nicht wesentlich von „Hallo Welt“ in JSX oder in TSX:
function component() { return ( <div> "Hello, world!" </div> ); }
Es gibt jedoch einige entscheidende Unterschiede, die wir selbst anhand dieses einfachen Beispiels erkennen können:
- Es gibt keine eingebetteten Sprachen; alles im ClojureScript-Beispiel ist entweder ein String, ein Schlüsselwort oder eine Liste.
- Es ist prägnant ; Listen bieten all die Ausdruckskraft, die wir brauchen, ohne die Redundanz von schließenden HTML-Tags.
Diese beiden kleinen Unterschiede haben enorme Auswirkungen, nicht nur darauf, wie Sie Code schreiben , sondern auch darauf, wie Sie sich ausdrücken!
Wie ist das, fragen Sie? Lassen Sie uns ins Getümmel springen und sehen, was ClojureScript sonst noch für uns bereithält …
- Erste Schritte mit der Programmiersprache Elm
- Erste Schritte mit der Programmiersprache Elixir
Bausteine
In diesem ClojureScript-Tutorial werde ich mich bemühen, nicht zu sehr darauf einzugehen, was Clojure[Script] so großartig macht (was eine Menge Dinge sind, aber ich schweife ab). Nichtsdestotrotz wird es nützlich sein, einige grundlegende Konzepte zu behandeln, damit es möglich ist, die Breite dessen zu erfassen, was wir hier tun können.
Für erfahrene Clojuristas und Lispianer können Sie gerne zum nächsten Abschnitt springen!
Es gibt drei Hauptkonzepte, die ich zuerst behandeln muss:
Schlüsselwörter
Clojure[Script] hat ein Konzept namens Schlüsselwort. Es liegt irgendwo zwischen einer konstanten Zeichenfolge (z. B. in Java) und einem Schlüssel. Sie sind symbolische Bezeichner, die sich selbst auswerten .
Beispielsweise bezieht sich das Schlüsselwort :cat
immer auf :cat
und niemals auf etwas anderes. Genau wie in Java könnte man sagen:
private static const String MY_KEY = "my_key"; // ... myMap.put(MY_KEY, thing); // ... myMap.get(MY_KEY);
…in Clojure hätte man einfach:
(assoc my-map :my-key thing) (my-map :my-key) ; equivalent to (:my-key my-map) ...nice and flexible!
Beachten Sie auch: In Clojure ist eine Map sowohl eine Sammlung (von Schlüsseln zu Werten, genau wie eine Java HashMap
) als auch eine Funktion für den Zugriff auf ihren Inhalt. Sauber!
Listen
Da Clojure[Script] ein Lisp-Dialekt ist, legt es großen Wert auf Listen. Wie ich bereits erwähnt habe, gibt es zwei wichtige Dinge zu beachten:
-
[]
ist ein Vektor, ähnlich wie eineArrayList
. -
()
ist eine Sequenz, ähnlich wie eineLinkedList
.
Um eine Liste von Dingen in Clojure[Script] zu erstellen, gehen Sie wie folgt vor:
[1 2 3 4] ["hello" "world"] ["my" "list" "contains" 10 "things"] ; you can mix and match types ; in Clojure lists!
Bei Sequenzen ist es etwas anders:
'(1 2 3 4) '("hello" "world")
Das vorangestellte '
wird im nächsten Abschnitt erklärt.
Funktionen
Schließlich haben wir Funktionen. Eine Funktion in Clojure[Script] ist eine Sequenz , die ohne das vorangestellte '
getippt wird. Das erste Element dieser Liste ist die Funktion selbst, und alle folgenden Elemente sind die Argumente . Zum Beispiel:
(+ 1 2 3 4) ; -> 10 (str "hello" " " "world") ; -> "hello world" (println "hi!") ; prints "hi!" to the console (run-my-function) ; runs the function named `run-my-function`
Eine Folge dieses Verhaltens ist, dass Sie eine Definition einer Funktion erstellen können, ohne sie tatsächlich auszuführen! Bei der Auswertung des Programms wird nur eine „nackte“ Sequenz ausgeführt.
(+ 1 1) ; -> 2 '(+ 1 1); -> a list of a function and two numbers
Dies wird später relevant!
Funktionen können auf verschiedene Arten definiert werden:
; A normal function definition, assigning the function ; to the symbol `my-function` (defn my-function [arg1 arg2] (+ arg1 arg2)) ; An anonymous function that does the same thing as the above (fn [arg1 arg2] (+ arg1 arg2)) ; Another, more concise variation of the above #(+ %1 %2)
Eine nähere Betrachtung
Nachdem wir nun die Grundlagen behandelt haben, wollen wir ein wenig mehr ins Detail gehen, um zu sehen, was hier passiert.
Reagieren in ClojureScript erfolgt im Allgemeinen mit einer Bibliothek namens Reagent. Reagent verwendet Hiccup und seine Syntax zur Darstellung von HTML. Aus dem Wiki des Hiccup-Repos:
„Hiccup verwandelt Clojure-Datenstrukturen wie folgt:“
[:a {:href "http://github.com"} "GitHub"]
„In HTML-Strings wie folgt:“
<a href="http://github.com">GitHub</a>
Einfach ausgedrückt, das erste Element der Liste wird zum HTML-Elementtyp und die verbleibenden werden zum Inhalt dieses Elements. Optional können Sie eine Zuordnung von Attributen bereitstellen, die dann an dieses Element angehängt wird.
Elemente können einfach ineinander verschachtelt werden, indem sie in der Liste ihrer Eltern verschachtelt werden! Am einfachsten sieht man das an einem Beispiel:
[:div [:h1 "This is a header"] [:p "And in the next element we have 1 + 1"] [:p (+ 1 1)]]
Beachten Sie, wie wir jede alte Funktion oder allgemeine Clojure-Syntax in unsere Struktur einfügen können, ohne explizit eine Einbettungsmethode deklarieren zu müssen. Es ist schließlich nur eine Liste!
Und noch besser, was wird zur Laufzeit ausgewertet?
[:div [:h1 "This is a header"] [:p "And in the next element we have 1 + 1"] [:p 2]]
Natürlich eine Liste mit Stichworten und Inhalten! Es gibt keine lustigen Typen, keine magischen versteckten Methoden. Es ist nur eine einfache alte Liste von Dingen. Sie können diese Liste beliebig erweitern und damit spielen – was Sie sehen, ist das, was Sie bekommen.

Da Hiccup das Layout übernimmt und Reagent die Logik und Ereignisverarbeitung übernimmt, erhalten wir am Ende eine voll funktionsfähige React-Umgebung.
Ein komplizierteres Beispiel
In Ordnung, lassen Sie uns das ein bisschen mehr mit einigen Komponenten verbinden. Eines der magischen Dinge an React (und Reagent) ist, dass Sie Ihre Ansichts- und Layoutlogik in Module unterteilen, die Sie dann in Ihrer gesamten Anwendung wiederverwenden können.
Angenommen, wir erstellen eine einfache Komponente, die eine Schaltfläche und eine einfache Logik zeigt:
; widget.cljs (defn component [polite?] [:div [:p (str "Do not press the button" (when polite? ", please."))] [:input {:type "button" :value "PUSH ME" :on-click #(js/alert "What did I tell you?")}]])
Kurzer Hinweis zur Benennung: Module in Clojure haben normalerweise Namensräume, daher könnte widget.cljs
unter dem Namensraum widget
importiert werden. Das bedeutet, dass auf die Komponentenfunktion der obersten Ebene als widget/component
component
wird. Ich mag es, nur eine Top-Level-Komponente pro Modul zu haben, aber das ist eine Stilpräferenz – vielleicht ziehen Sie es vor, Ihre Komponentenfunktion so zu benennen wie polite-component
oder widget-component
.
Diese einfache Komponente präsentiert uns ein optionales Höflichkeits-Widget. (when polite? ", please.")
ergibt ", please."
Wann polite? == true
polite? == true
und auf nil
, wenn es false
ist.
Lassen Sie uns dies nun in unsere app.cljs
:
(defn app [] [:div [:h1 "Welcome to my app"] [widget/component true]])
Hier betten wir unser Widget in unsere App-Komponente ein, indem wir es als erstes Element einer Liste aufrufen – genau wie die HTML-Schlüsselwörter! Wir können dann alle untergeordneten Elemente oder Parameter an die Komponente übergeben, indem wir sie als andere Elemente derselben Liste bereitstellen. Hier übergeben wir einfach true
, also in unserem Widget polite? == true
polite? == true
, und so erhalten wir die höfliche Version.
Würden wir jetzt unsere App-Funktion evaluieren, kämen wir auf folgendes:
[:div [:h1 "Welcome to my app"] [widget/component true]] ; <- widget/component would look more like a ; function reference, but I have kept it ; clean for legibility.
Beachten Sie, dass das widget/component
nicht ausgewertet wurde! (Siehe den Abschnitt Funktionen, wenn Sie verwirrt sind.)
Komponenten innerhalb Ihres DOM-Baums werden nur ausgewertet (und somit hinter den Kulissen in echte React-Objekte umgewandelt), wenn sie aktualisiert wurden, was die Dinge schön und bissig hält und die Menge an Komplexität reduziert, mit der Sie sich zu jedem Zeitpunkt auseinandersetzen müssen.
Weitere Einzelheiten zu diesem Thema für Interessierte finden Sie in den Reagenziendokumenten.
Listen bis ganz nach unten
Beachten Sie außerdem, dass das DOM nur eine Liste von Listen ist und Komponenten nur Funktionen sind, die Listen von Listen zurückgeben. Warum ist das so wichtig, wenn Sie ClojureScript lernen?
Denn alles, was Sie mit Funktionen oder Listen tun können, können Sie auch mit Komponenten tun.
Hier beginnen Sie, zusammengesetzte Renditen zu erzielen, indem Sie einen Lisp-Dialekt wie ClojureScript verwenden: Ihre Komponenten und HTML-Elemente werden zu erstklassigen Objekten, die Sie wie alle anderen normalen Daten manipulieren können! Lassen Sie mich das einfach noch einmal sagen:
Komponenten und HTML-Elemente sind erstklassige unterstützte Objekte innerhalb der Clojure-Sprache!
Das ist richtig, du hast mich gehört. Es ist fast so, als ob Lisps entwickelt wurden, um Listen zu verarbeiten (Hinweis: Sie waren es.)
Dazu gehören Dinge wie:
- Mapping über Elemente einer nummerierten Liste:
(def words ["green" "eggs" "and" "ham"]) (defn li-shout [x] [:li (string/uppercase x)) (concat [:ol] (map li-shout words) ; becomes [:ol [:li "GREEN"] [:li "EGGS"] [:li "AND"] [:li "HAM"]]
- Verpackungskomponenten:
; in widget.cljs (defn greeting-component [name] [:div [:p (str "Hiya " name "!")]]) ; ... (def shouty-greeting-component #(widget/greeting-component (string/uppercase %))) (defn app [] [:div [:h1 "My App"] [shouty-greeting-component "Luke"]]) ; <- will show Hiya LUKE!
- Attribute einfügen:
(def default-btn-attrs {:type "button" :value "I am a button" :class "my-button-class"}) (defn two-button-component [] [:div [:input (assoc default-btn-attrs :on-click #(println "I do one thing"))] [:input (assoc default-btn-attrs :on-click #(println "I do a different thing"))]])
Der Umgang mit einfachen alten Datentypen wie Listen und Karten ist dramatisch einfacher als alles, was einer Klasse ähnelt, und ist auf lange Sicht viel leistungsfähiger!
Ein Muster entsteht
Okay, fassen wir noch einmal zusammen. Was hat unser ClojureScript-Tutorial bisher gezeigt?
- Alles ist auf die einfachste Funktionalität reduziert – Elemente sind nur Listen und Komponenten sind nur Funktionen, die Elemente zurückgeben.
- Da Komponenten und Elemente erstklassige Objekte sind, können wir mit weniger mehr schreiben.
Diese beiden Punkte passen genau in das Ethos von Clojure und der funktionalen Programmierung – Code sind zu manipulierende Daten , und Komplexität wird durch Verbinden weniger komplexer Teile aufgebaut. Wir stellen unser Programm (in diesem Beispiel unsere Webseite) als Daten (Listen, Funktionen, Karten) dar und belassen es bis zum allerletzten Moment, wenn Reagent übernimmt und es in React-Code umwandelt. Dies macht unseren Code wiederverwendbar und vor allem sehr einfach zu lesen und mit sehr wenig Magie zu verstehen.
Stylisch werden
Jetzt wissen wir, wie man eine Anwendung mit einigen grundlegenden Funktionen erstellt, also lassen Sie uns damit fortfahren, wie wir sie gut aussehen lassen können. Es gibt mehrere Möglichkeiten, dies zu erreichen, am einfachsten ist es, Stylesheets zu verwenden und ihre Klassen in Ihren Komponenten zu referenzieren:
.my-class { color: red; }
[:div {:class "my-class"} "Hello, world!"]
Dies wird genau das tun, was Sie erwarten würden, und uns ein wunderschönes, rotes „Hello, world!“ präsentieren. Text.
Aber warum sollten Sie sich all diese Mühe machen, Ansichts- und Logikcode in Ihren Komponenten zusammenzufassen, aber dann Ihr Styling in ein Stylesheet aufzuteilen – Sie müssen jetzt nicht nur an zwei verschiedenen Stellen suchen, sondern Sie haben es auch mit zwei verschiedenen zu tun Sprachen auch!
Warum schreiben Sie unser CSS nicht als Code in unsere Komponenten (siehe Thema hier?). Dies bringt uns eine Reihe von Vorteilen:
- Alles, was eine Komponente definiert, befindet sich an derselben Stelle.
- Klassennamen können durch clevere Generierung garantiert eindeutig sein.
- CSS kann dynamisch sein und sich ändern, wenn sich unsere Daten ändern.
Meine persönliche Lieblingsvariante von CSS-in-Code ist mit Clojure Style Sheets (cljss). Eingebettetes CSS sieht wie folgt aus:
;; -- STYLES ------------------------------------------------------------ (defstyles component-style [] {:color "red" :width "100%"}) ;; -- VIEW -------------------------------------------------------------- (defn component [] [:div {:class (component-style)} "Hello, world!"])
defstyles
erstellt eine Funktion, die einen eindeutigen Klassennamen für uns generiert (was großartig für jeden ist, der unsere Komponenten importiert).
Es gibt viele andere Dinge, die cljss für Sie tun kann (Komposition von Stilen, Animationen, Elementüberschreibungen usw.), auf die ich hier nicht näher eingehen werde. Ich empfehle, dass Sie es selbst überprüfen!
Zusammenbau der Einzelteile einer ClojureScript-App
Schließlich gibt es noch den Kleber, der benötigt wird, um all dies zusammenzuhalten. Glücklicherweise gibt es hier neben einer Projektdatei und einer index.html
mindestens Boilerplate.
Du brauchst:
- Ihre Projektdefinitionsdatei
project.clj
. Dies ist ein fester Bestandteil jedes Clojure-Projekts und definiert Ihre Abhängigkeiten – sogar direkt von GitHub – und andere Build-Eigenschaften (ähnlich wiebuild.gradle
oderpackage.json
.) - Eine
index.html
, die als Bindungspunkt für die Reagenzanwendung dient. - Etwas Setup-Code für eine Entwicklungsumgebung und schließlich zum Starten Ihrer Reagent-Anwendung.
Das vollständige Codebeispiel für dieses ClojureScript-Tutorial finden Sie auf GitHub.
Das war es also (vorerst). Hoffentlich habe ich Ihre Neugierde zumindest ein wenig geweckt, sei es, um einen Lisp-Dialekt (Clojure [Script] oder andere) zu testen oder sich sogar an der Erstellung Ihrer eigenen Reagenzanwendung zu versuchen! Ich verspreche dir, dass du es nicht bereuen wirst.
Begleiten Sie mich im Anschluss an diesen Artikel „In einen Zustand kommen“, in dem ich über Zustandsverwaltung mit Re-Frame spreche – sagen Sie Hallo zu Redux in ClojureScript!