Odkrywanie ClojureScript do programowania front-end

Opublikowany: 2022-03-11

Podczas czytania tego wstępu w Twojej głowie krążą prawdopodobnie dwie główne myśli:

  1. Co to jest ClojureScript?
  2. To nie ma związku z moimi zainteresowaniami.

Ale poczekaj! Tutaj się mylisz – a ja ci to udowodnię. Jeśli chcesz zainwestować 10 minut swojego czasu, pokażę Ci, jak ClojureScript może sprawić, że pisanie aplikacji typu front-end i React-y będzie zabawne, szybkie i – co najważniejsze – funkcjonalne .

Niektóre wymagania wstępne samouczka ClojureScript

  • Znajomość seplenienia nie jest wymagana. Zrobię co w mojej mocy, aby wyjaśnić wszelkie próbki kodu, które są rozproszone w tym poście na blogu!
  • Jeśli jednak chcesz zrobić trochę wstępnego czytania, gorąco polecam https://www.braveclojure.com/, punkt kompleksowej obsługi, aby zacząć korzystać z Clojure (i co za tym idzie, ClojureScript).
  • Clojure i ClojureScript mają wspólny język — często będę się do nich odwoływać w tym samym czasie, co Clojure[Script] .
  • Zakładam, że masz wiedzę na temat Reacta i ogólną wiedzę na temat front-endu.

Jak nauczyć się ClojureScript: krótka wersja

Nie masz więc dużo czasu na naukę ClojureScript, a po prostu chcesz zobaczyć, dokąd zmierza to wszystko. Po pierwsze, czym jest ClojureScript?

Ze strony ClojureScript: ClojureScript to kompilator Clojure, który jest skierowany do JavaScript. Emituje kod JavaScript, który jest zgodny z zaawansowanym trybem kompilacji kompilatora optymalizującego Google Closure.

ClojureScript ma między innymi wiele do zaoferowania:

  • Jest to wieloparadygmatyczny język programowania z uproszczonym programowaniem funkcjonalnym — wiadomo, że programowanie funkcjonalne poprawia czytelność kodu , a także pomaga pisać więcej przy mniejszej ilości kodu .
  • Domyślnie obsługuje niezmienność — pożegnaj się z całym pakietem problemów ze środowiskiem wykonawczym!
  • Jest zorientowany na dane: kod to dane w ClojureScript. Większość aplikacji Clojure[Script] można zredukować do zestawu funkcji, które działają na pewnej podstawowej strukturze danych, co sprawia, że ​​debugowanie jest proste, a kod bardzo czytelny.
  • To proste! Rozpoczęcie pracy z ClojureScript jest łatwe — nie ma wymyślnych słów kluczowych i bardzo mało magii.
  • Ma fantastyczną bibliotekę standardową. Ta rzecz ma wszystko.

Pomijając to, otwórzmy tę puszkę robaków na przykładzie:

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

Uwaga dla tych, którzy nie znają dialektów Lisp lub ClojureScript: Najważniejsze części tego przykładu to :div , [] i () . :div to słowo kluczowe reprezentujące element <div> . [] jest wektorem, podobnie jak ArrayList w Javie, a () jest sekwencją, podobnie jak LinkedList . Omówię to bardziej szczegółowo w dalszej części tego postu!

Jest to najbardziej podstawowa forma komponentu React w ClojureScript. To wszystko — tylko słowo kluczowe, ciąg znaków i cała masa list.

Pshaw! mówisz, nie różni się to znacząco od „hello world” w JSX lub TSX:

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

Jest jednak kilka istotnych różnic, które możemy dostrzec nawet na tym podstawowym przykładzie:

  • Nie ma wbudowanych języków; wszystko w przykładzie ClojureScript jest ciągiem, słowem kluczowym lub listą.
  • Jest zwięzły ; listy zapewniają pełną ekspresję, jakiej potrzebujemy, bez nadmiarowych znaczników zamykających HTML.

Te dwie małe różnice mają ogromne konsekwencje, nie tylko w sposobie pisania kodu, ale także w sposobie wyrażania siebie!

Jak to jest, pytasz? Wskoczmy do walki i zobaczmy, co jeszcze ClojureScript ma dla nas w przygotowaniu…

Związane z:
  • Pierwsze kroki z językiem programowania Elm
  • Pierwsze kroki z językiem programowania Elixir

Cegiełki

W tym samouczku ClojureScript postaram się nie zagłębiać zbytnio w to, co sprawia, że ​​Clojure[Script] jest wspaniały (co jest wieloma rzeczami, ale robię dygresję). Niemniej jednak przyda się omówienie kilku podstawowych pojęć, aby móc zrozumieć, co możemy tutaj zrobić.

Dla doświadczonych Clojuristas i Lispians, nie wahaj się przejść do następnej sekcji!

Najpierw muszę omówić trzy główne koncepcje:

Słowa kluczowe

Clojure[Skrypt] ma pojęcie zwane słowem kluczowym. Leży gdzieś pomiędzy stałym ciągiem (powiedzmy, w Javie) a kluczem. Są to symboliczne identyfikatory, które oceniają się same .

Na przykład słowo kluczowe :cat zawsze będzie odnosić się do :cat , a nigdy do niczego innego. Tak jak w Javie można powiedzieć:

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

…w Clojure po prostu miałbyś:

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

Uwaga: w Clojure mapa jest zarówno kolekcją (kluczy do wartości, jak Java HashMap ), jak i funkcją dostępu do jej zawartości. Schludny!

Listy

Clojure[Script] jest dialektem Lispu, co oznacza, że ​​kładzie duży nacisk na listy. Jak wspomniałem wcześniej, należy pamiętać o dwóch głównych rzeczach:

  1. [] jest wektorem, podobnie jak ArrayList .
  2. () to sekwencja, podobnie jak LinkedList .

Aby zbudować listę rzeczy w Clojure[Skrypt], wykonaj następujące czynności:

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

W przypadku sekwencji jest trochę inaczej:

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

Znak poprzedzający ' wyjaśniony w następnej sekcji.

Funkcje

Wreszcie mamy funkcje. Funkcja w Clojure[Script] to sekwencja , która jest wpisywana bez poprzedzającego ' . Pierwszym elementem tej listy jest sama funkcja, a wszystkie kolejne elementy będą argumentami . Na przykład:

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

Jedną z konsekwencji tego zachowania jest to, że możesz zbudować definicję funkcji bez faktycznego jej wykonywania! Tylko "naga" sekwencja zostanie wykonana podczas oceny programu.

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

Będzie to miało znaczenie później!

Funkcje można definiować na kilka sposobów:

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

Bliższe badanie

Więc teraz, gdy omówiliśmy podstawy, zagłębimy się w nieco więcej szczegółów, aby zobaczyć, co się tutaj dzieje.

React w ClojureScript jest zazwyczaj wykonywany przy użyciu biblioteki o nazwie Reagent. Reagent używa Czkawki i jej składni do reprezentowania HTML. Z wiki repozytorium Czkawka:

„Czuwka zmienia struktury danych Clojure w ten sposób:”

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

„W ciągi HTML w ten sposób:”

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

Mówiąc najprościej, pierwszy element listy staje się typem elementu HTML, a pozostałe stają się zawartością tego elementu. Opcjonalnie możesz dostarczyć mapę atrybutów, które zostaną następnie dołączone do tego elementu.

Elementy można zagnieżdżać w sobie nawzajem, po prostu zagnieżdżając je na liście ich rodzica! Najprościej widać to na przykładzie:

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

Zwróć uwagę, jak możemy umieścić dowolną starą funkcję lub ogólną składnię Clojure w naszej strukturze bez konieczności jawnego deklarowania metody osadzania. W końcu to tylko lista!

A jeszcze lepiej, jak to się ocenia w czasie wykonywania?

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

Oczywiście lista słów kluczowych i treści! Nie ma śmiesznych typów ani magicznych ukrytych metod. To tylko zwykła stara lista rzeczy. Możesz dowolnie łączyć i bawić się tą listą — dostajesz to, co widzisz.

Czkawka tworzy układ, a Reagent logikę i przetwarzanie zdarzeń, dzięki czemu otrzymujemy w pełni funkcjonalne środowisko React.

Bardziej skomplikowany przykład

W porządku, połączmy to trochę bardziej z niektórymi komponentami. Jedną z magicznych cech Reacta (i Reagenta) jest to, że dzielisz logikę widoku i układu na moduły, które możesz ponownie wykorzystać w całej aplikacji.

Załóżmy, że tworzymy prosty komponent, który pokazuje przycisk i prostą logikę:

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

Krótka uwaga na temat nazewnictwa: moduły w Clojure są zwykle w przestrzeni nazw, więc widget.cljs może być zaimportowany do widget przestrzeni nazw . Oznacza to, że funkcja component najwyższego poziomu będzie dostępna jako widget/component . Lubię mieć tylko jeden składnik najwyższego poziomu na moduł, ale jest to preferencja stylu — możesz woleć nazwać funkcję komponentu w stylu polite-component lub widget-component .

Ten prosty komponent przedstawia nam opcjonalnie uprzejmy widget. (when polite? ", please.") daje wynik ", please." kiedy polite? == true polite? == true i nil , gdy jest false .

Teraz osadźmy to w naszym app.cljs :

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

Tutaj osadzamy nasz widżet w naszym komponencie aplikacji, wywołując go jako pierwszy element listy — podobnie jak słowa kluczowe HTML! Następnie możemy przekazać dowolne elementy potomne lub parametry do komponentu, podając je jako inne elementy tej samej listy. Tutaj po prostu przekazujemy true , więc w naszym widżecie polite? == true polite? == true , a więc otrzymujemy wersję grzewczą.

Gdybyśmy mieli teraz ocenić naszą funkcję aplikacji, otrzymalibyśmy:

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

Zwróć uwagę, że widget/component nie został oceniony! (Jeśli jesteś zdezorientowany, zobacz sekcję Funkcje).

Komponenty w twoim drzewie DOM są oceniane (a tym samym przekształcane w prawdziwe obiekty React za kulisami), jeśli zostały zaktualizowane, co sprawia, że ​​wszystko jest przyjemne i zgrabne oraz zmniejsza stopień złożoności, z którą musisz sobie poradzić w dowolnym momencie.

Więcej szczegółów na ten temat dla zainteresowanych można uzyskać w dokumentacji dotyczącej odczynników.

Listy do końca

Co więcej, zauważ, że DOM to tylko lista list, a komponenty to tylko funkcje, które zwracają listy list. Dlaczego jest to tak ważne, gdy uczysz się ClojureScript?

Ponieważ wszystko, co możesz zrobić z funkcjami lub listami, możesz zrobić z komponentami.

Tutaj zaczynasz uzyskiwać zwroty złożone, używając dialektu Lisp, takiego jak ClojureScript: Twoje komponenty i elementy HTML stają się obiektami pierwszej klasy, którymi możesz manipulować jak innymi normalnymi danymi! Powiem tylko to jeszcze raz:

Komponenty i elementy HTML są pierwszorzędnymi obsługiwanymi obiektami w języku Clojure!

Zgadza się, słyszałeś mnie. To prawie tak, jakby Lispy zostały zaprojektowane do przetwarzania list (podpowiedź: były).

Obejmuje to takie rzeczy jak:

  • Mapowanie elementów listy numerowanej:
 (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"]]
  • Owijanie elementów:
 ; 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!
  • Wstrzykiwanie atrybutów:
 (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"))]])

Radzenie sobie ze zwykłymi starymi typami danych, takimi jak listy i mapy, jest znacznie prostsze niż cokolwiek przypominającego klasę, a na dłuższą metę jest znacznie potężniejsze!

Pojawia się wzór

Ok, podsumujmy. Co do tej pory pokazał nasz samouczek ClojureScript?

  • Wszystko sprowadza się do najprostszej funkcjonalności — elementy to tylko listy, a komponenty to tylko funkcje zwracające elementy.
  • Ponieważ komponenty i elementy są obiektami pierwszej klasy, jesteśmy w stanie pisać więcej za mniej .

Te dwa punkty ściśle pasują do Clojure i etosu programowania funkcjonalnego — kod to dane , którymi należy manipulować, a złożoność buduje się poprzez łączenie mniej złożonych części. Prezentujemy nasz program (w tym przykładzie naszą stronę internetową) jako dane (listy, funkcje, mapy) i przechowujemy je tak do ostatniego momentu, kiedy Reagent przejmie kontrolę i zamieni go w kod React. Dzięki temu nasz kod można ponownie wykorzystać, a co najważniejsze, jest bardzo łatwy do odczytania i zrozumienia przy bardzo niewielkiej ilości magii.

Ubieranie się stylowe

Teraz wiemy, jak zrobić aplikację z podstawową funkcjonalnością, więc przejdźmy do tego, jak możemy sprawić, by wyglądała dobrze. Jest kilka sposobów podejścia do tego, najprostszy jest z arkuszami stylów i odwoływaniem się do ich klas w twoich komponentach:

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

Uda się dokładnie tak, jak byś się spodziewał, prezentując nam piękne, czerwone „Hello, world!” tekst.

Jednak po co zajmować się tymi wszystkimi problemami, łącząc widok i kod logiczny w swoich komponentach, a następnie oddzielając stylizację w arkuszu stylów — nie tylko teraz musisz szukać w dwóch różnych miejscach, ale masz do czynienia z dwoma różnymi języki też!

Dlaczego nie napisać naszego kodu CSS jako kodu w naszych komponentach (zobacz motyw tutaj?). Da nam to szereg korzyści:

  • Wszystko, co definiuje komponent, znajduje się w tym samym miejscu.
  • Nazwy klas mogą być zagwarantowane niepowtarzalne dzięki sprytnemu generowaniu.
  • CSS może być dynamiczny, zmieniając się wraz ze zmianami naszych danych.

Moją ulubioną odmianą CSS w kodzie są arkusze stylów Clojure (cljss). Wbudowany CSS wygląda jak poniżej:

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

defstyles tworzy funkcję, która wygeneruje dla nas unikalną nazwę klasy (co jest świetne dla każdego, kto importuje nasze komponenty).

Jest wiele innych rzeczy, które cljss może dla ciebie zrobić (komponowanie stylów, animacji, nadpisywanie elementów itp.), których nie będę tutaj omawiał. Polecam, abyś sam to sprawdził!

Składanie fragmentów aplikacji ClojureScript

Wreszcie jest klej potrzebny do sklejenia tego wszystkiego razem. Na szczęście oprócz pliku projektu i index.html , boilerplate jest tutaj minimalny.

Potrzebujesz:

  • Twój plik definicji projektu, project.clj . Podstawa każdego projektu Clojure, definiuje twoje zależności — nawet bezpośrednio z GitHub — i inne właściwości kompilacji (podobne do build.gradle lub package.json ).
  • Plik index.html , który działa jako punkt wiązania dla aplikacji Reagent.
  • Trochę kodu instalacyjnego dla środowiska deweloperskiego i wreszcie do uruchomienia aplikacji Reagent.

Pełny przykład kodu dla tego samouczka ClojureScript znajdziesz w serwisie GitHub.

Więc to wszystko (na razie). Mam nadzieję, że przynajmniej trochę wzbudziłem twoją ciekawość, czy to po to, by sprawdzić dialekt Lisp (Clojure[Skrypt] lub inny), czy nawet spróbować swoich sił w stworzeniu własnej aplikacji odczynników! Obiecuję, że nie pożałujesz.

Dołącz do mnie w kontynuacji tego artykułu Getting Into a State, w którym mówię o zarządzaniu stanem za pomocą re-frame — przywitaj się z Redux w ClojureScript!