Découvrir ClojureScript pour le développement frontal
Publié: 2022-03-11Il y a probablement deux pensées principales dans votre tête pendant que vous lisez cette introduction :
- Qu'est-ce que ClojureScript ?
- Ce n'est pas pertinent pour mes intérêts.
Mais attendez! C'est là que vous vous trompez et je vais vous le prouver. Si vous êtes prêt à investir 10 minutes de votre temps, je vais vous montrer comment ClojureScript peut rendre l'écriture d'applications front-end et React-y amusante, rapide et, surtout , fonctionnelle .
Quelques prérequis du didacticiel ClojureScript
- La connaissance de Lisp n'est pas requise. Je ferai de mon mieux pour expliquer tous les exemples de code dispersés dans cet article de blog !
- Cependant, si vous souhaitez faire une petite lecture préalable, je vous recommande vivement https://www.braveclojure.com/, le guichet unique pour démarrer avec Clojure (et par extension, ClojureScript).
- Clojure et ClojureScript partagent un langage commun - je les désignerai souvent en même temps que Clojure[Script] .
- Je suppose que vous avez une connaissance de React et un savoir-faire général sur le front-end.
Comment apprendre ClojureScript : la version courte
Vous n'avez donc pas beaucoup de temps pour apprendre ClojureScript, et vous voulez juste voir où tout cela va. Tout d'abord, qu'est-ce que ClojureScript ?
Du site Web ClojureScript : ClojureScript est un compilateur pour Clojure qui cible JavaScript. Il émet du code JavaScript compatible avec le mode de compilation avancé du compilateur d'optimisation Google Closure.
Entre autres choses, ClojureScript a beaucoup à offrir :
- Il s'agit d'un langage de programmation multiparadigme avec une programmation fonctionnelle simplifiée. La programmation fonctionnelle est connue pour améliorer la lisibilité du code et vous aider à écrire plus avec moins de code .
- Il prend en charge l'immuabilité par défaut. Dites adieu à toute une série de problèmes d'exécution !
- Il est orienté données : le code est une donnée dans ClojureScript. La plupart des applications Clojure[Script] peuvent être réduites à un ensemble de fonctions qui fonctionnent sur une structure de données sous-jacente, ce qui rend le débogage simple et le code super lisible.
- C'est simple! Démarrer avec ClojureScript est facile - il n'y a pas de mots-clés fantaisistes et très peu de magie.
- Il a une bibliothèque standard fantastique. Cette chose a tout.
Avec cela à l'écart, ouvrons cette boîte de Pandore avec un exemple :
(defn component [] [:div "Hello, world!"])
Remarque pour ceux qui ne sont pas familiers avec les dialectes Lisp ou ClojureScript : les parties les plus importantes de cet exemple sont le :div
, le []
et le ()
. :div
est un mot clé représentant l'élément <div>
. []
est un vecteur, un peu comme un ArrayList
en Java, et ()
est une séquence, un peu comme un LinkedList
. J'aborderai cela plus en détail plus tard dans ce post!
Il s'agit de la forme la plus basique d'un composant React dans ClojureScript. C'est tout, juste un mot-clé, une chaîne et tout un tas de listes.
Peuh! vous dites, ce n'est pas très différent de "hello world" dans JSX ou dans TSX :
function component() { return ( <div> "Hello, world!" </div> ); }
Cependant, il existe des différences cruciales que nous pouvons repérer même à partir de cet exemple de base :
- Il n'y a pas de langages intégrés ; tout dans l'exemple ClojureScript est soit une chaîne, soit un mot-clé, soit une liste.
- C'est concis ; les listes fournissent toute l'expressivité dont nous avons besoin sans la redondance des balises de fermeture HTML.
Ces deux petites différences ont d'énormes conséquences, non seulement sur la façon dont vous écrivez du code, mais aussi sur la façon dont vous vous exprimez !
Comment est-ce, demandez-vous? Lançons-nous dans la mêlée et voyons ce que ClojureScript nous réserve d'autre…
- Premiers pas avec le langage de programmation Elm
- Premiers pas avec le langage de programmation Elixir
Blocs de construction
Tout au long de ce didacticiel ClojureScript, je m'efforcerai de ne pas creuser trop loin dans ce qui rend Clojure[Script] génial (ce qui est beaucoup de choses, mais je m'éloigne du sujet). Néanmoins, il sera utile de couvrir certains concepts de base afin qu'il soit possible de saisir l'étendue de ce que nous pouvons faire ici.
Pour les clojuristes et lispiens expérimentés, n'hésitez pas à passer à la section suivante !
Il y a trois concepts principaux que je devrai aborder en premier :
Mots clés
Clojure[Script] a un concept appelé mot-clé. Il se situe quelque part entre une chaîne constante (par exemple, en Java) et une clé. Ce sont des identificateurs symboliques qui s'évaluent eux-mêmes .
Par exemple, le mot-clé :cat
fera toujours référence à :cat
et jamais à autre chose. Tout comme en Java, vous pourriez dire :
private static const String MY_KEY = "my_key"; // ... myMap.put(MY_KEY, thing); // ... myMap.get(MY_KEY);
…dans Clojure vous auriez simplement :
(assoc my-map :my-key thing) (my-map :my-key) ; equivalent to (:my-key my-map) ...nice and flexible!
A noter également : dans Clojure, une carte est à la fois une collection (de clés de valeurs, tout comme un HashMap
Java) et une fonction permettant d'accéder à son contenu. Soigné!
Listes
Clojure[Script] étant un dialecte Lisp signifie qu'il met beaucoup l'accent sur les listes. Comme je l'ai mentionné plus tôt, il y a deux choses principales à savoir :
-
[]
est un vecteur, un peu comme unArrayList
. -
()
est une séquence, un peu comme uneLinkedList
.
Pour créer une liste de choses dans Clojure[Script], procédez comme suit :
[1 2 3 4] ["hello" "world"] ["my" "list" "contains" 10 "things"] ; you can mix and match types ; in Clojure lists!
Pour les séquences, c'est un peu différent :
'(1 2 3 4) '("hello" "world")
L'ajout '
est expliqué dans la section suivante.
Les fonctions
Enfin, nous avons des fonctions. Une fonction dans Clojure[Script] est une séquence qui est tapée sans le préfixe '
. Le premier élément de cette liste est la fonction elle-même, et tous les éléments suivants seront les arguments . Par exemple:
(+ 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 corollaire de ce comportement est que vous pouvez construire une définition d'une fonction sans l'exécuter réellement ! Seule une séquence 'nue' sera exécutée lors de l'évaluation du programme.
(+ 1 1) ; -> 2 '(+ 1 1); -> a list of a function and two numbers
Cela deviendra pertinent plus tard!
Les fonctions peuvent être définies de plusieurs manières :
; 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 examen plus approfondi
Alors maintenant que nous avons couvert les bases, approfondissons un peu plus les détails pour voir ce qui se passe ici.
React dans ClojureScript se fait généralement à l'aide d'une bibliothèque appelée Reagent. Reagent utilise Hiccup et sa syntaxe pour représenter le HTML. Du wiki du dépôt Hiccup :
"Hiccup transforme les structures de données Clojure comme ceci :"
[:a {:href "http://github.com"} "GitHub"]
"Dans des chaînes de code HTML comme ceci :"
<a href="http://github.com">GitHub</a>
En termes simples, le premier élément de la liste devient le type d'élément HTML et le reste devient le contenu de cet élément. En option, vous pouvez fournir une carte d'attributs qui sera ensuite attachée à cet élément.
Les éléments peuvent être imbriqués les uns dans les autres simplement en les imbriquant dans la liste de leur parent ! Ceci est plus facile à voir avec un exemple:
[:div [:h1 "This is a header"] [:p "And in the next element we have 1 + 1"] [:p (+ 1 1)]]
Remarquez comment nous pouvons mettre n'importe quelle ancienne fonction ou syntaxe générale de Clojure dans notre structure sans avoir à déclarer explicitement une méthode d'incorporation. Ce n'est qu'une liste, après tout !
Et encore mieux, à quoi cela correspond-il lors de l'exécution ?
[:div [:h1 "This is a header"] [:p "And in the next element we have 1 + 1"] [:p 2]]
Une liste de mots clés et de contenus bien sûr ! Il n'y a pas de types amusants, pas de méthodes magiques cachées. C'est juste une vieille liste de choses. Vous pouvez assembler et jouer avec cette liste autant que vous le souhaitez - ce que vous voyez est ce que vous obtenez.

Avec Hiccup faisant la mise en page et Reagent faisant la logique et le traitement des événements, nous nous retrouvons avec un environnement React entièrement fonctionnel.
Un exemple plus compliqué
Très bien, relions cela un peu plus avec certains composants. L'une des choses magiques de React (et de Reagent) est que vous compartimentez votre vue et votre logique de mise en page en modules, que vous pouvez ensuite réutiliser dans votre application.
Supposons que nous créons un composant simple qui affiche un bouton et une logique simple :
; 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?")}]])
Remarque rapide sur la dénomination : les modules dans Clojure sont normalement dotés d'un espace de noms, donc widget.cljs
peut être importé sous le widget
espace de noms. Cela signifie que la fonction de component
de niveau supérieur sera accessible en tant que widget/component
. J'aime n'avoir qu'un seul composant de niveau supérieur par module, mais c'est une préférence de style - vous préférerez peut-être nommer votre fonction de composant quelque chose comme polite-component
ou widget-component
.
Ce composant simple nous présente un widget éventuellement poli. (when polite? ", please.")
évalué à ", please."
quand polite? == true
polite? == true
et à nil
quand c'est false
.
Maintenant, intégrons ceci dans notre app.cljs
:
(defn app [] [:div [:h1 "Welcome to my app"] [widget/component true]])
Ici, nous intégrons notre widget dans notre composant d'application en l'appelant comme le premier élément d'une liste, tout comme les mots-clés HTML ! Nous pouvons ensuite transmettre tous les enfants ou paramètres au composant en les fournissant comme d'autres éléments de la même liste. Ici, nous passons simplement true
, donc dans notre widget polite? == true
polite? == true
, et nous obtenons ainsi la version polie.
Si nous devions évaluer la fonction de notre application maintenant, nous obtiendrions ce qui suit :
[: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.
Notez que le widget/component
n'a pas été évalué ! (Voir la section Fonctions si vous êtes confus.)
Les composants de votre arborescence DOM ne sont évalués (et donc convertis en véritables objets React dans les coulisses) que s'ils ont été mis à jour, ce qui rend les choses agréables et vives et réduit la complexité à laquelle vous devez faire face à tout moment.
Plus de détails à ce sujet pour les personnes intéressées peuvent être obtenus dans la documentation sur les réactifs.
Listes tout le long
De plus, notez que le DOM n'est qu'une liste de listes et que les composants ne sont que des fonctions qui renvoient des listes de listes. Pourquoi est-ce si important lorsque vous apprenez ClojureScript ?
Parce que tout ce que vous pouvez faire aux fonctions ou aux listes, vous pouvez le faire aux composants.
C'est là que vous commencez à obtenir des rendements composés en utilisant un dialecte Lisp comme ClojureScript : vos composants et éléments HTML deviennent des objets de première classe que vous pouvez manipuler comme n'importe quelle autre donnée normale ! Permettez-moi de le répéter :
Les composants et les éléments HTML sont des objets pris en charge de première classe dans le langage Clojure !
C'est vrai, vous m'avez entendu. C'est presque comme si Lisps avait été conçu pour traiter des listes (indice : ils l'étaient.)
Cela inclut des éléments tels que :
- Mappage sur les éléments d'une liste numérotée :
(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"]]
- Composants d'emballage :
; 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!
- Attributs d'injection :
(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"))]])
Traiter avec des types de données anciens tels que des listes et des cartes est considérablement plus simple que tout ce qui ressemble à une classe, et finit par être beaucoup plus puissant à long terme !
Un modèle émerge
Bon, récapitulons. Qu'est-ce que notre tutoriel ClojureScript a montré jusqu'à présent ?
- Tout est réduit à la fonctionnalité la plus simple : les éléments ne sont que des listes et les composants ne sont que des fonctions qui renvoient des éléments.
- Parce que les composants et les éléments sont des objets de première classe, nous sommes capables d'écrire plus avec moins .
Ces deux points s'intègrent parfaitement dans la philosophie de Clojure et de la programmation fonctionnelle - le code est une donnée à manipuler, et la complexité s'accumule en connectant des parties moins complexes. Nous présentons notre programme (notre page Web dans cet exemple) sous forme de données (listes, fonctions, cartes) et le gardons ainsi jusqu'au tout dernier moment où Reagent prend le relais et le transforme en code React. Cela rend notre code réutilisable, et surtout très facile à lire et à comprendre avec très peu de magie.
Devenir élégant
Nous savons maintenant comment créer une application avec certaines fonctionnalités de base, alors passons à la façon dont nous pouvons la rendre belle. Il existe plusieurs façons d'aborder cela, la plus simple étant d'utiliser des feuilles de style et de référencer leurs classes dans vos composants :
.my-class { color: red; }
[:div {:class "my-class"} "Hello, world!"]
Cela fera exactement ce à quoi vous vous attendez, nous présentant un beau "Hello, world!" texte.
Cependant, pourquoi se donner tant de mal pour colocaliser la vue et le code logique dans vos composants, mais séparer ensuite votre style dans une feuille de style - non seulement vous devez maintenant regarder à deux endroits différents, mais vous avez affaire à deux différents les langues aussi !
Pourquoi ne pas écrire notre CSS sous forme de code dans nos composants (voir un thème ici ?). Cela nous donnera un tas d'avantages:
- Tout ce qui définit un composant est au même endroit.
- Les noms de classe peuvent être garantis uniques grâce à une génération intelligente.
- CSS peut être dynamique, changeant à mesure que nos données changent.
Ma saveur personnelle préférée de CSS-in-code est avec les feuilles de style Clojure (cljss). Le CSS intégré ressemble à ce qui suit :
;; -- STYLES ------------------------------------------------------------ (defstyles component-style [] {:color "red" :width "100%"}) ;; -- VIEW -------------------------------------------------------------- (defn component [] [:div {:class (component-style)} "Hello, world!"])
defstyles
crée une fonction qui générera un nom de classe unique pour nous (ce qui est idéal pour quiconque importe nos composants.)
Il y a beaucoup d'autres choses que cljss peut faire pour vous (composer des styles, des animations, des remplacements d'éléments, etc.) que je n'entrerai pas dans les détails ici. Je vous recommande de vérifier vous-même!
Assemblage des éléments d'une application ClojureScript
Enfin, il y a la colle nécessaire pour coller tout cela ensemble. Heureusement, outre un fichier de projet et un index.html
, le passe-partout est au minimum ici.
Vous avez besoin:
- Votre fichier de définition de projet,
project.clj
. Un élément de base de tout projet Clojure, cela définit vos dépendances, même directement à partir de GitHub, et d'autres propriétés de construction (similaire àbuild.gradle
oupackage.json
.) - Un
index.html
qui agit comme un point de liaison pour l'application Reagent. - Du code de configuration pour un environnement de développement et enfin pour démarrer votre application Reagent.
Vous trouverez l'exemple de code complet pour ce tutoriel ClojureScript disponible sur GitHub.
Alors c'est tout (pour l'instant). J'espère avoir au moins un peu piqué votre curiosité, que ce soit pour découvrir un dialecte Lisp (Clojure[Script] ou autre) ou même pour vous essayer à créer votre propre application Reagent ! Je vous promets que vous ne le regretterez pas.
Rejoignez-moi dans le suivi de cet article, Getting Into a State, où je parle de la gestion de l'état à l'aide du recadrage - dites bonjour à Redux dans ClojureScript !