Scoperto ClojureScript per lo sviluppo front-end

Pubblicato: 2022-03-11

Probabilmente ci sono due pensieri principali nella tua testa mentre leggi questa introduzione:

  1. Cos'è ClojureScript?
  2. Questo non è rilevante per i miei interessi.

Ma aspetta! È qui che ti sbagli, e te lo dimostrerò. Se sei disposto a investire 10 minuti del tuo tempo, ti mostrerò come ClojureScript può rendere la scrittura di applicazioni front-end e React-y divertente, veloce e, soprattutto, funzionale .

Alcuni prerequisiti del tutorial ClojureScript

  • La conoscenza del Lisp non è richiesta. Farò del mio meglio per spiegare eventuali esempi di codice sparsi in questo post del blog!
  • Tuttavia, se vuoi fare una piccola pre-lettura, ti consiglio vivamente https://www.braveclojure.com/, lo sportello unico per iniziare con Clojure (e, per estensione, ClojureScript).
  • Clojure e ClojureScript condividono un linguaggio comune: mi riferirò spesso a questi contemporaneamente a Clojure[Script] .
  • Presumo che tu abbia conoscenza di React e del know-how generale del front-end.

Come imparare ClojureScript: la versione breve

Quindi non hai molto tempo per imparare ClojureScript e vuoi solo vedere dove sta andando tutto questo. Per prima cosa, cos'è ClojureScript?

Dal sito Web ClojureScript: ClojureScript è un compilatore per Clojure che ha come target JavaScript. Emette codice JavaScript compatibile con la modalità di compilazione avanzata del compilatore di ottimizzazione di Google Closure.

Tra le altre cose, ClojureScript ha molto da offrire:

  • È un linguaggio di programmazione multiparadigma con una programmazione funzionale snella: è noto che la programmazione funzionale migliora la leggibilità del codice e ti aiuta a scrivere di più con meno codice .
  • Supporta l'immutabilità per impostazione predefinita: dì addio a un'intera suite di problemi di runtime!
  • È orientato ai dati: il codice è dati in ClojureScript. La maggior parte delle applicazioni Clojure[Script] può essere ridotta a un insieme di funzioni che operano su alcune strutture di dati sottostanti, il che rende il debug semplice e il codice super leggibile.
  • È semplice! Iniziare con ClojureScript è facile: non ci sono parole chiave fantasiose e pochissima magia.
  • Ha una fantastica libreria standard. Questa cosa ha tutto.

Detto questo, apriamo questo barattolo di vermi con un esempio:

 (defn component [] [:div "Hello, world!"])

Nota per coloro che non hanno familiarità con i dialetti Lisp o ClojureScript: le parti più importanti di questo esempio sono :div , [] e () . :div è una parola chiave che rappresenta l'elemento <div> . [] è un vettore, proprio come un ArrayList in Java, e () è una sequenza, proprio come un LinkedList . Toccherò questo in modo più dettagliato più avanti in questo post!

Questa è la forma più semplice di un componente React in ClojureScript. Questo è tutto: solo una parola chiave, una stringa e un intero gruppo di elenchi.

Psciò! dici che non è significativamente diverso da "ciao mondo" in JSX o in TSX:

 function component() { return ( <div> "Hello, world!" </div> ); }

Tuttavia, ci sono alcune differenze cruciali che possiamo individuare anche da questo esempio di base:

  • Non ci sono lingue incorporate; tutto all'interno dell'esempio ClojureScript è una stringa, una parola chiave o un elenco.
  • È conciso ; gli elenchi forniscono tutta l'espressività di cui abbiamo bisogno senza la ridondanza dei tag di chiusura HTML.

Queste due piccole differenze hanno enormi conseguenze, non solo nel modo in cui scrivi il codice, ma anche nel modo in cui ti esprimi!

Com'è, chiedi? Saltiamo nella mischia e vediamo cos'altro ClojureScript ha in serbo per noi...

Imparentato:
  • Introduzione al linguaggio di programmazione Elm
  • Introduzione al linguaggio di programmazione Elisir

Costruzioni

Durante questo tutorial su ClojureScript, cercherò di non scavare troppo in ciò che rende Clojure[Script] eccezionale (che è un sacco di cose, ma sto divagando). Tuttavia, sarà utile avere alcuni concetti di base coperti in modo da poter cogliere l'ampiezza di ciò che possiamo fare qui.

Per gli esperti Clojuristi e Lispiani, sentiti libero di saltare alla sezione successiva!

Ci sono tre concetti principali che dovrò affrontare prima:

Parole chiave

Clojure[Script] ha un concetto chiamato Parola chiave. Si trova da qualche parte tra una stringa costante (diciamo, in Java) e una chiave. Sono identificatori simbolici che valutano se stessi .

Ad esempio, la parola chiave :cat si riferirà sempre a :cat e mai a nient'altro. Proprio come in Java potresti dire:

 private static const String MY_KEY = "my_key"; // ... myMap.put(MY_KEY, thing); // ... myMap.get(MY_KEY);

…a Clojure avresti semplicemente:

 (assoc my-map :my-key thing) (my-map :my-key) ; equivalent to (:my-key my-map) ...nice and flexible!

Nota anche: in Clojure, una mappa è sia una raccolta (di chiavi per valori, proprio come una HashMap Java) sia una funzione per accedere ai suoi contenuti. Pulito!

Elenchi

Clojure[Script] essendo un dialetto Lisp significa che pone molta enfasi sulle liste. Come ho detto prima, ci sono due cose principali da tenere a mente:

  1. [] è un vettore, molto simile a un ArrayList .
  2. () è una sequenza, molto simile a un LinkedList .

Per creare un elenco di cose in Clojure[Script], procedi come segue:

 [1 2 3 4] ["hello" "world"] ["my" "list" "contains" 10 "things"] ; you can mix and match types ; in Clojure lists!

Per le sequenze, è leggermente diverso:

 '(1 2 3 4) '("hello" "world")

L'anteposto ' è spiegato nella sezione successiva.

Funzioni

Infine, abbiamo le funzioni. Una funzione in Clojure[Script] è una sequenza che viene digitata senza anteporre ' . Il primo elemento di tale elenco è la funzione stessa e tutti i seguenti elementi saranno gli argomenti . Per esempio:

 (+ 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`

Un corollario di questo comportamento è che puoi creare una definizione di una funzione senza eseguirla effettivamente! Solo una sequenza 'nuda' verrà eseguita quando il programma viene valutato.

 (+ 1 1) ; -> 2 '(+ 1 1); -> a list of a function and two numbers

Questo diventerà rilevante in seguito!

Le funzioni possono essere definite in diversi modi:

 ; 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)

Un esame più approfondito

Quindi ora che abbiamo coperto le basi, analizziamo un po' più nel dettaglio per vedere cosa sta succedendo qui.

Reagire in ClojureScript viene generalmente eseguito utilizzando una libreria chiamata Reagent. Reagent usa Hiccup e la sua sintassi per rappresentare l'HTML. Dal wiki del repository Hiccup:

"Hiccup trasforma le strutture dati di Clojure in questo modo:"

 [:a {:href "http://github.com"} "GitHub"]

"In stringhe di HTML come questa:"

 <a href="http://github.com">GitHub</a>

In poche parole, il primo elemento dell'elenco diventa il tipo di elemento HTML e il restante diventa il contenuto di quell'elemento. Facoltativamente, puoi fornire una mappa di attributi che verrà quindi allegata a quell'elemento.

Gli elementi possono essere nidificati l'uno nell'altro semplicemente nidificandoli nell'elenco del loro genitore! Questo è più facile da vedere con un esempio:

 [:div [:h1 "This is a header"] [:p "And in the next element we have 1 + 1"] [:p (+ 1 1)]]

Nota come possiamo inserire qualsiasi vecchia funzione o sintassi Clojure generale all'interno della nostra struttura senza dover dichiarare esplicitamente un metodo di incorporamento. È solo una lista, dopotutto!

E ancora meglio, cosa valuta questo in fase di esecuzione?

 [:div [:h1 "This is a header"] [:p "And in the next element we have 1 + 1"] [:p 2]]

Un elenco di parole chiave e contenuti ovviamente! Non ci sono tipi divertenti, nessun metodo magico nascosto. È solo un semplice vecchio elenco di cose. Puoi unire e giocare con questo elenco quanto vuoi: quello che vedi è quello che ottieni.

Con Hiccup che esegue il layout e Reagent che esegue la logica e l'elaborazione degli eventi, ci ritroviamo con un ambiente React completamente funzionale.

Un esempio più complicato

Va bene, leghiamo un po' di più con alcuni componenti. Una delle cose magiche di React (e Reagent) è che compartimenti la tua vista e la logica del layout in moduli, che puoi quindi riutilizzare in tutta la tua applicazione.

Supponiamo di creare un semplice componente che mostra un pulsante e una semplice logica:

 ; 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?")}]])

Nota rapida sulla denominazione: i moduli in Clojure hanno normalmente uno spazio dei nomi, quindi widget.cljs potrebbe essere importato sotto il widget dello spazio dei nomi . Ciò significa che si accederà alla funzione del component di livello superiore come widget/component . Mi piace avere un solo componente di primo livello per modulo, ma questa è una preferenza di stile: potresti preferire dare un nome alla funzione del tuo componente come polite-component o widget-component .

Questo semplice componente ci presenta un widget facoltativamente educato. (when polite? ", please.") restituisce ", please." quando polite? == true polite? == true e su nil quando è false .

Ora incorporiamo questo nel nostro app.cljs :

 (defn app [] [:div [:h1 "Welcome to my app"] [widget/component true]])

Qui incorporiamo il nostro widget all'interno del nostro componente app chiamandolo come primo elemento di un elenco, proprio come le parole chiave HTML! Possiamo quindi passare eventuali figli o parametri al componente fornendoli come altri elementi della stessa lista. Qui passiamo semplicemente true , quindi nel nostro widget polite? == true polite? == true , e quindi otteniamo la versione educata.

Se dovessimo valutare la nostra funzione dell'app ora, otterremmo quanto segue:

 [: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.

Nota come il widget/component non è stato valutato! (Se sei confuso, consulta la sezione Funzioni.)

I componenti all'interno del tuo albero DOM vengono valutati (e quindi convertiti in veri oggetti React dietro le quinte) solo se sono stati aggiornati, il che mantiene le cose belle e scattanti e riduce la quantità di complessità che devi affrontare in qualsiasi momento.

Maggiori dettagli su questo argomento per gli interessati possono essere ottenuti nei documenti Reagent.

Elenca fino in fondo

Inoltre, nota come il DOM sia solo un elenco di elenchi e i componenti siano solo funzioni che restituiscono elenchi di elenchi. Perché è così importante mentre impari ClojureScript?

Perché tutto ciò che puoi fare per funzioni o elenchi, puoi farlo per i componenti.

È qui che inizi a ottenere rendimenti compositi utilizzando un dialetto Lisp come ClojureScript: i tuoi componenti e gli elementi HTML diventano oggetti di prima classe che puoi manipolare come qualsiasi altro dato normale! Lascia che lo ripeta ancora:

I componenti e gli elementi HTML sono oggetti supportati di prima classe all'interno del linguaggio Clojure!

Esatto, mi hai sentito. È quasi come se i Lisps fossero stati progettati per elaborare elenchi (suggerimento: lo erano.)

Questo include cose come:

  • Mappatura degli elementi di un elenco numerato:
 (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"]]
  • Componenti di avvolgimento:
 ; 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!
  • Iniezione di attributi:
 (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"))]])

Gestire semplici tipi di dati vecchi come elenchi e mappe è drammaticamente più semplice di qualsiasi cosa assomigli a una classe e finisce per essere molto più potente a lungo termine!

Emerge un modello

Ok, ricapitoliamo. Che cosa ha mostrato finora il nostro tutorial ClojureScript?

  • Tutto è ridotto alla funzionalità più semplice: gli elementi sono solo elenchi e i componenti sono solo funzioni che restituiscono elementi.
  • Poiché i componenti e gli elementi sono oggetti di prima classe, siamo in grado di scrivere di più con meno .

Questi due punti si adattano perfettamente all'etica di Clojure e della programmazione funzionale: il codice è dati da manipolare e la complessità si costruisce collegando parti meno complesse. Presentiamo il nostro programma (la nostra pagina web in questo esempio) come dati (elenchi, funzioni, mappe) e lo manteniamo così fino all'ultimo momento in cui Reagent prende il sopravvento e lo trasforma in codice React. Questo rende il nostro codice riutilizzabile e, soprattutto, molto facile da leggere e comprendere con pochissima magia.

Diventare alla moda

Ora sappiamo come creare un'applicazione con alcune funzionalità di base, quindi passiamo a come renderla bella. Ci sono un paio di modi per avvicinarsi a questo, il più semplice è con i fogli di stile e facendo riferimento alle loro classi nei componenti:

 .my-class { color: red; }
 [:div {:class "my-class"} "Hello, world!"]

Questo farà esattamente come ti aspetteresti, presentandoci un bellissimo "Hello, world!" rosso testo.

Tuttavia, perché affrontare tutti questi problemi nel collocare la vista e il codice logico nei componenti, ma poi separare il tuo stile in un foglio di stile: non solo ora devi guardare in due posti diversi, ma hai a che fare con due diversi anche le lingue!

Perché non scrivere il nostro CSS come codice nei nostri componenti (vedi un tema qui?). Questo ci darà una serie di vantaggi:

  • Tutto ciò che definisce un componente è nello stesso posto.
  • I nomi delle classi possono essere garantiti unici attraverso una generazione intelligente.
  • I CSS possono essere dinamici, cambiando al variare dei nostri dati.

Il mio gusto personale preferito di CSS-in-code è con Clojure Style Sheets (cljss). Il CSS incorporato è simile al seguente:

 ;; -- STYLES ------------------------------------------------------------ (defstyles component-style [] {:color "red" :width "100%"}) ;; -- VIEW -------------------------------------------------------------- (defn component [] [:div {:class (component-style)} "Hello, world!"])

defstyles crea una funzione che genererà un nome di classe univoco per noi (il che è ottimo per chiunque importi i nostri componenti).

Ci sono molte altre cose che cljss può fare per te (stili di composizione, animazioni, sostituzioni di elementi, ecc.) che non entrerò nei dettagli qui. Ti consiglio di verificarlo tu stesso!

Assemblare i pezzi di un'app ClojureScript

Infine, c'è la colla necessaria per incollare tutto questo insieme. Fortunatamente, oltre a un file di progetto e un index.html , boilerplate è almeno qui.

Hai bisogno:

  • Il tuo file di definizione del progetto, project.clj . Un punto fermo di qualsiasi progetto Clojure, questo definisce le tue dipendenze, anche direttamente da GitHub, e altre proprietà di build (simili a build.gradle o package.json .)
  • Un index.html che funge da punto di legame per l'applicazione Reagent.
  • Alcuni codici di configurazione per un ambiente di sviluppo e infine per avviare l'applicazione Reagent.

Troverai l'esempio di codice completo per questo tutorial ClojureScript disponibile su GitHub.

Quindi è tutto (per ora). Spero di aver almeno stuzzicato un po' la tua curiosità, sia che si tratti di controllare un dialetto Lisp (Clojure[Script] o altro) o anche di provare a creare la tua applicazione Reagente! Ti prometto che non te ne pentirai.

Unisciti a me nel seguito di questo articolo, Entrare in uno stato, in cui parlo della gestione dello stato utilizzando il re-frame: saluta Redux in ClojureScript!