Dezgroparea ClojureScript pentru dezvoltarea front-end

Publicat: 2022-03-11

Probabil că există două gânduri principale în capul tău în timp ce citești această introducere:

  1. Ce este ClojureScript?
  2. Acest lucru nu este relevant pentru interesele mele.

Dar asteapta! Acolo greșești — și ți-o voi dovedi. Dacă ești dispus să investești 10 minute din timpul tău, îți voi arăta cum ClojureScript poate face scrierea aplicațiilor front-end și React-y distractivă, rapidă și, cel mai important , funcțională .

Câteva cerințe preliminare pentru tutorialul ClojureScript

  • Nu sunt necesare cunoștințe de Lisp. Voi face tot posibilul să explic orice eșantioane de cod care sunt împrăștiate în această postare de blog!
  • Cu toate acestea, dacă doriți să citiți puțin înainte, v-aș recomanda cu căldură https://www.braveclojure.com/, ghișeul unic pentru a începe cu Clojure (și prin extensie, ClojureScript).
  • Clojure și ClojureScript împărtășesc un limbaj comun - deseori mă voi referi la acestea în același timp și Clojure[Script] .
  • Presupun că aveți cunoștințe despre React și cunoștințe generale de front-end.

Cum să înveți ClojureScript: versiunea scurtă

Deci nu ai mult timp să înveți ClojureScript și vrei doar să vezi încotro se duce toată treaba asta. În primul rând, ce este ClojureScript?

De pe site-ul web ClojureScript: ClojureScript este un compilator pentru Clojure care vizează JavaScript. Emite cod JavaScript care este compatibil cu modul avansat de compilare al compilatorului de optimizare Google Closure.

Printre altele, ClojureScript are multe de oferit:

  • Este un limbaj de programare multiparadigmă cu o programare funcțională – programarea funcțională este cunoscută pentru că îmbunătățește lizibilitatea codului și vă ajută să scrieți mai mult cu mai puțin cod .
  • Acceptă imuabilitatea în mod implicit - spune la revedere unei întregi suită de probleme de rulare!
  • Este orientat către date: codul este date în ClojureScript. Majoritatea aplicațiilor Clojure[Script] pot fi reduse la un set de funcții care operează pe o structură de date subiacentă, ceea ce face depanarea simplă și codul super-lizibil.
  • E simplu! Începeți cu ClojureScript este ușor – nu există cuvinte cheie luxoase și foarte puțină magie.
  • Are o bibliotecă standard fantastică. Chestia asta are de toate.

Cu asta din drum, să deschidem această cutie de viermi cu un exemplu:

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

Notă pentru cei care nu sunt familiarizați cu dialectele Lisp sau ClojureScript: Cele mai importante părți ale acestui exemplu sunt :div , [] și () . :div este un cuvânt cheie care reprezintă elementul <div> . [] este un vector, la fel ca un ArrayList în Java, iar () este o secvență, la fel ca un LinkedList . Voi aborda acest lucru în detaliu mai târziu în această postare!

Aceasta este cea mai de bază formă a unei componente React în ClojureScript. Asta este — doar un cuvânt cheie, un șir și o grămadă de liste.

Pshaw! spuneți că nu diferă semnificativ de „hello world” în JSX sau în TSX:

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

Cu toate acestea, există câteva diferențe cruciale pe care le putem observa chiar și din acest exemplu de bază:

  • Nu există limbi încorporate; totul din exemplul ClojureScript este fie un șir, un cuvânt cheie sau o listă.
  • Este concis ; Listele oferă toată expresivitatea de care avem nevoie fără redundanța etichetelor de închidere HTML.

Aceste două mici diferențe au consecințe uriașe, nu doar în modul în care scrieți codul, ci și în modul în care vă exprimați!

Cum e asta, te întrebi? Să intrăm în luptă și să vedem ce ne mai rezervă ClojureScript...

Legate de:
  • Noțiuni introductive cu limbajul de programare Elm
  • Noțiuni introductive cu limbajul de programare Elixir

Blocuri de construcție

De-a lungul acestui tutorial ClojureScript, mă voi strădui să nu aprofundez prea mult în ceea ce face Clojure[Script] grozav (ceea ce reprezintă o mulțime de lucruri, dar mă opresc). Cu toate acestea, va fi util să avem câteva concepte de bază acoperite, astfel încât să fie posibil să înțelegem amploarea a ceea ce putem face aici.

Pentru cei cu experiență Clojuristi și Lispians, nu ezitați să treceți la următoarea secțiune!

Există trei concepte principale pe care va trebui să le acopăr mai întâi:

Cuvinte cheie

Clojure[Script] are un concept numit cuvânt cheie. Se află undeva între un șir constant (să zicem, în Java) și o cheie. Sunt identificatori simbolici care se evaluează singuri .

De exemplu, cuvântul cheie :cat se va referi întotdeauna la :cat și niciodată la nimic altceva. La fel ca în Java, ați putea spune:

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

… în Clojure ai avea pur și simplu:

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

De asemenea, rețineți: în Clojure, o hartă este atât o colecție (de chei pentru valori, la fel ca un Java HashMap ) și o funcție pentru accesarea conținutului său. Îngrijit!

Liste

Clojure[Script] fiind un dialect Lisp înseamnă că pune mult accent pe liste. După cum am menționat mai devreme, există două lucruri principale de care trebuie să fii conștient:

  1. [] este un vector, la fel ca un ArrayList .
  2. () este o secvență, la fel ca un LinkedList .

Pentru a construi o listă de lucruri în Clojure[Script], faceți următoarele:

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

Pentru secvențe, este puțin diferit:

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

Precedatul ' este explicat în secțiunea următoare.

Funcții

În sfârșit, avem funcții. O funcție în Clojure[Script] este o secvență care este scrisă fără antecedentele ' . Primul element al listei este funcția în sine, iar toate elementele următoare vor fi argumentele . De exemplu:

 (+ 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 corolar al acestui comportament este că puteți construi o definiție a unei funcții fără a o executa efectiv! Doar o secvență „naked” va fi executată atunci când programul este evaluat.

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

Acest lucru va deveni relevant mai târziu!

Funcțiile pot fi definite în câteva moduri:

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

O examinare mai atentă

Așa că acum că am acoperit elementele de bază, haideți să detaliem puțin pentru a vedea ce se întâmplă aici.

React în ClojureScript se face în general folosind o bibliotecă numită Reactiv. Reactiv folosește Hiccup și sintaxa sa pentru a reprezenta HTML. Din wiki-ul lui Hiccup repo:

„Hicup transformă structurile de date Clojure astfel:”

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

„În șiruri de HTML ca acesta:”

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

Mai simplu spus, primul element al listei devine tipul de element HTML, iar restul devin conținutul acelui element. Opțional, puteți furniza o hartă a atributelor care vor fi apoi atașate acelui element.

Elementele pot fi imbricate unul în celălalt pur și simplu prin imbricarea lor în lista părintelui lor! Acest lucru se vede cel mai ușor cu un exemplu:

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

Observați cum putem pune orice funcție veche sau sintaxă generală Clojure în structura noastră fără a fi nevoie să declarăm în mod explicit o metodă de încorporare. Este doar o listă, până la urmă!

Și chiar mai bine, la ce evaluează acest lucru în timpul execuției?

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

O listă de cuvinte cheie și conținut, desigur! Nu există tipuri amuzante, nu există metode magice ascunse. Este doar o listă veche de lucruri. Puteți îmbina și juca cu această listă cât de mult doriți - ceea ce vedeți este ceea ce obțineți.

Cu Hiccup care face aspectul și Reagent care face logica și procesarea evenimentelor, ajungem la un mediu React complet funcțional.

Un exemplu mai complicat

În regulă, hai să legăm asta ceva mai mult cu câteva componente. Unul dintre lucrurile magice despre React (și Reagent) este că vă compartimentați vizualizarea și logica aspectului în module, pe care apoi le puteți reutiliza în întreaga aplicație.

Să presupunem că creăm o componentă simplă care arată un buton și o logică simplă:

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

Notă rapidă privind denumirea: modulele din Clojure sunt în mod normal spațiate de nume, astfel încât widget.cljs ar putea fi importat sub widget spațiului de nume . Aceasta înseamnă că funcția de component de nivel superior va fi accesată ca widget/component . Îmi place să am o singură componentă de nivel superior per modul, dar aceasta este o preferință de stil - ați putea prefera să denumiți funcția dvs. componente cum ar fi polite-component sau widget-component .

Această componentă simplă ne prezintă un widget opțional politicos. (when polite? ", please.") evaluează la ", please." când polite? == true polite? == true și la nil când este false .

Acum să încorporăm acest lucru în app.cljs :

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

Aici încorporam widget-ul nostru în componenta aplicației, numind-o ca primul element al unei liste, la fel ca și cuvintele cheie HTML! Apoi putem trece orice copii sau parametri la componentă furnizându-i ca alte elemente din aceeași listă. Aici trecem pur și simplu true , deci în widget-ul nostru polite? == true polite? == true și astfel obținem versiunea politicoasă.

Dacă ar fi să evaluăm funcția aplicației noastre acum, am obține următoarele:

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

Observați cum widget/component nu a fost evaluată! (Consultați secțiunea Funcții dacă sunteți confuz.)

Componentele din arborele dvs. DOM sunt evaluate (și astfel convertite în obiecte reale React în culise) numai dacă au fost actualizate, ceea ce menține lucrurile frumoase și rapide și reduce cantitatea de complexitate cu care trebuie să vă ocupați în orice moment.

Mai multe detalii despre acest subiect pentru cei interesați pot fi obținute în documentele Reactiv.

Liste până în jos

Mai mult, rețineți că DOM-ul este doar o listă de liste, iar componentele sunt doar funcții care returnează liste de liste. De ce este atât de important când înveți ClojureScript?

Pentru că orice puteți face cu funcții sau liste, puteți face componente.

Aici începeți să obțineți randamente compuse folosind un dialect Lisp precum ClojureScript: Componentele și elementele HTML devin obiecte de primă clasă pe care le puteți manipula ca orice alte date normale! Lasă-mă să spun asta din nou:

Componentele și elementele HTML sunt obiecte acceptate de primă clasă în limbajul Clojure!

Așa e, m-ai auzit. Este aproape ca și cum Lisps ar fi fost conceput pentru a procesa liste (indiciu: au fost.)

Aceasta include lucruri precum:

  • Maparea elementelor unei liste numerotate:
 (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"]]
  • Componente de ambalare:
 ; 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!
  • Injectarea atributelor:
 (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"))]])

Tratarea cu tipuri de date vechi simple, cum ar fi listele și hărțile, este dramatic mai simplă decât orice seamănă cu o clasă și ajunge să fie mult mai puternică pe termen lung!

Apare un model

Bine, să recapitulăm. Ce a arătat până acum tutorialul nostru ClojureScript?

  • Totul se reduce la cea mai simplă funcționalitate - elementele sunt doar liste, iar componentele sunt doar funcții care returnează elemente.
  • Deoarece componentele și elementele sunt obiecte de primă clasă, suntem capabili să scriem mai mult cu mai puțin .

Aceste două puncte se potrivesc perfect în etosul Clojure și al programării funcționale – codul reprezintă date care trebuie manipulate, iar complexitatea este construită prin conectarea părților mai puțin complexe. Prezentăm programul nostru (pagina noastră web în acest exemplu) ca date (liste, funcții, hărți) și îl păstrăm așa până în ultimul moment când Reagent preia controlul și îl transformă în cod React. Acest lucru face codul nostru reutilizabil și, cel mai important, foarte ușor de citit și de înțeles cu foarte puțină magie.

Devin stilat

Acum știm cum să facem o aplicație cu unele funcționalități de bază, așa că haideți să trecem la cum o putem face să arate bine. Există câteva moduri de a aborda acest lucru, cel mai simplu fiind cu foi de stil și referirea la clasele acestora în componentele dvs.:

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

Acest lucru se va descurca exact așa cum v-ați aștepta, prezentându-ne un frumos, roșu „Bună, lume!” text.

Totuși, de ce să mergi la toate aceste probleme cu colocarea vizualizării și a codului logic în componentele tale, dar apoi să-ți desprinzi stilul într-o foaie de stil - nu numai că acum trebuie să cauți în două locuri diferite, dar ai de-a face cu două diferite? si limbi!

De ce să nu scriem CSS-ul nostru ca cod în componentele noastre (vedeți o temă aici?). Acest lucru ne va oferi o mulțime de avantaje:

  • Tot ceea ce definește o componentă este în același loc.
  • Numele claselor pot fi garantate unice prin generare inteligentă.
  • CSS poate fi dinamic, schimbându-se pe măsură ce datele noastre se schimbă.

Aromă personală preferată de CSS-in-code este cu Clojure Style Sheets (cljss). CSS încorporat arată ca mai jos:

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

defstyles creează o funcție care va genera un nume de clasă unic pentru noi (ceea ce este grozav pentru oricine importă componentele noastre.)

Există multe alte lucruri pe care cljss le poate face pentru tine (stiluri de compunere, animații, suprascrieri de elemente etc.) pe care nu voi intra în detaliu aici. Vă recomand să verificați singur!

Asamblarea pieselor unei aplicații ClojureScript

În cele din urmă, există lipiciul necesar pentru a lipi toate acestea împreună. Din fericire, pe lângă un fișier de proiect și un index.html , boilerplate este la minimum aici.

Ai nevoie:

  • Fișierul dvs. de definiție a proiectului, project.clj . Un element de bază al oricărui proiect Clojure, acesta vă definește dependențele - chiar și direct din GitHub - și alte proprietăți de compilare (similar cu build.gradle sau package.json .)
  • Un index.html care acționează ca punct de legare pentru aplicația Reactiv.
  • Un cod de configurare pentru un mediu de dezvoltare și, în sfârșit, pentru pornirea aplicației dvs. de reactiv.

Veți găsi exemplul complet de cod pentru acest tutorial ClojureScript disponibil pe GitHub.

Deci asta este (deocamdată). Sper că v-am stârnit curiozitatea măcar puțin, fie că este vorba de a verifica un dialect Lisp (Clojure[Script] sau altfel) sau chiar de a vă încerca să vă faceți propria aplicație Reactiv! Îți promit că nu vei regreta.

Alăturați-vă mie în continuarea acestui articol, Getting In a State, unde vorbesc despre managementul de stat folosind re-frame—să salutați Redux în ClojureScript!