Descubriendo ClojureScript para el desarrollo front-end
Publicado: 2022-03-11Probablemente hay dos pensamientos principales en tu cabeza mientras lees esta introducción:
- ¿Qué es ClojureScript?
- Esto no es relevante para mis intereses.
¡Pero espera! Ahí es donde te equivocas, y te lo demostraré. Si está dispuesto a invertir 10 minutos de su tiempo, le mostraré cómo ClojureScript puede hacer que escribir aplicaciones frontales y React-y sea divertido, rápido y, lo más importante , funcional .
Algunos requisitos previos del tutorial de ClojureScript
- No se requiere conocimiento de lisp. ¡Haré todo lo posible para explicar los ejemplos de código que se encuentran dispersos en esta publicación de blog!
- Sin embargo, si desea leer un poco antes, le recomiendo https://www.braveclojure.com/, la ventanilla única para comenzar con Clojure (y, por extensión, ClojureScript).
- Clojure y ClojureScript comparten un lenguaje común; a menudo me referiré a estos al mismo tiempo como Clojure[Script] .
- Supongo que tiene conocimiento de React y conocimientos generales de front-end.
Cómo aprender ClojureScript: la versión corta
Así que no tienes mucho tiempo para aprender ClojureScript, y solo quieres ver a dónde va todo esto. Lo primero es lo primero, ¿qué es ClojureScript?
Del sitio web de ClojureScript: ClojureScript es un compilador para Clojure que apunta a JavaScript. Emite código JavaScript que es compatible con el modo de compilación avanzado del compilador de optimización Google Closure.
Entre otras cosas, ClojureScript tiene mucho que ofrecer:
- Es un lenguaje de programación multiparadigma con un enfoque de programación funcional: se sabe que la programación funcional mejora la legibilidad del código y lo ayuda a escribir más con menos código .
- Es compatible con la inmutabilidad de forma predeterminada: ¡diga adiós a un conjunto completo de problemas de tiempo de ejecución!
- Está orientado a datos: el código es datos en ClojureScript. La mayoría de las aplicaciones Clojure[Script] se pueden reducir a un conjunto de funciones que operan en alguna estructura de datos subyacente, lo que hace que la depuración sea simple y el código superlegible.
- ¡Es simple! Comenzar con ClojureScript es fácil: no hay palabras clave sofisticadas y muy poca magia.
- Tiene una fantástica biblioteca estándar. Esta cosa lo tiene todo.
Con eso fuera del camino, abramos esta lata de gusanos con un ejemplo:
(defn component [] [:div "Hello, world!"])
Nota para aquellos que no estén familiarizados con los dialectos Lisp o ClojureScript: las partes más importantes de este ejemplo son :div
, []
y ()
. :div
es una palabra clave que representa el elemento <div>
. []
es un vector, como un ArrayList
en Java, y ()
es una secuencia, como un LinkedList
. ¡Tocaré esto con más detalle más adelante en esta publicación!
Esta es la forma más básica de un componente React en ClojureScript. Eso es todo: solo una palabra clave, una cadena y un montón de listas.
¡Bah! dices, eso no es significativamente diferente de "hola mundo" en JSX o en TSX:
function component() { return ( <div> "Hello, world!" </div> ); }
Sin embargo, hay algunas diferencias cruciales que podemos detectar incluso a partir de este ejemplo básico:
- No hay lenguajes incrustados; todo dentro del ejemplo de ClojureScript es una cadena, una palabra clave o una lista.
- Es conciso ; las listas proporcionan toda la expresividad que necesitamos sin la redundancia de las etiquetas de cierre HTML.
¡Estas dos pequeñas diferencias tienen enormes consecuencias, no solo en cómo escribes el código, sino también en cómo te expresas!
¿Cómo es eso, te preguntarás? Saltemos a la refriega y veamos qué más ClojureScript tiene guardado para nosotros...
- Primeros pasos con el lenguaje de programación Elm
- Primeros pasos con el lenguaje de programación Elixir
Bloques de construcción
A lo largo de este tutorial de ClojureScript, me esforzaré por no profundizar demasiado en lo que hace que Clojure[Script] sea genial (que son muchas cosas, pero estoy divagando). No obstante, será útil tener algunos conceptos básicos cubiertos para que sea posible comprender la amplitud de lo que podemos hacer aquí.
Para aquellos clojuristas y lispianos experimentados, ¡no duden en pasar a la siguiente sección!
Hay tres conceptos principales que necesitaré cubrir primero:
Palabras clave
Clojure[Script] tiene un concepto llamado palabra clave. Se encuentra en algún lugar entre una cadena constante (por ejemplo, en Java) y una clave. Son identificadores simbólicos que se evalúan a sí mismos .
Como ejemplo, la palabra clave :cat
siempre se referirá a :cat
y nunca a otra cosa. Al igual que en Java, podrías decir:
private static const String MY_KEY = "my_key"; // ... myMap.put(MY_KEY, thing); // ... myMap.get(MY_KEY);
…en Clojure simplemente tendrías:
(assoc my-map :my-key thing) (my-map :my-key) ; equivalent to (:my-key my-map) ...nice and flexible!
También tenga en cuenta: en Clojure, un mapa es tanto una colección (de claves de valores, como un HashMap
de Java) como una función para acceder a su contenido. ¡Limpio!
Liza
Clojure[Script] siendo un dialecto Lisp significa que pone mucho énfasis en las listas. Como mencioné anteriormente, hay dos cosas principales a tener en cuenta:
-
[]
es un vector, muy parecido a unArrayList
. -
()
es una secuencia, muy parecida a unaLinkedList
.
Para construir una lista de cosas en Clojure[Script], haga lo siguiente:
[1 2 3 4] ["hello" "world"] ["my" "list" "contains" 10 "things"] ; you can mix and match types ; in Clojure lists!
Para secuencias, es un poco diferente:
'(1 2 3 4) '("hello" "world")
El prefijo '
se explica en la siguiente sección.
Funciones
Finalmente, tenemos las funciones. Una función en Clojure[Script] es una secuencia que se escribe sin el prefijo '
. El primer elemento de esa lista es la función misma, y todos los elementos siguientes serán los argumentos . Por ejemplo:
(+ 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 corolario de este comportamiento es que puede crear una definición de una función sin ejecutarla realmente! Solo se ejecutará una secuencia 'desnuda' cuando se evalúe el programa.
(+ 1 1) ; -> 2 '(+ 1 1); -> a list of a function and two numbers
¡Esto será relevante más adelante!
Las funciones se pueden definir de varias maneras:
; 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 más detallado
Entonces, ahora que hemos cubierto los conceptos básicos, profundicemos un poco más para ver qué está sucediendo aquí.
React en ClojureScript generalmente se realiza utilizando una biblioteca llamada Reagent. Reagent usa Hiccup y su sintaxis para representar HTML. De la wiki de Hiccup repo:
“Hiccup convierte las estructuras de datos de Clojure de esta manera:”
[:a {:href "http://github.com"} "GitHub"]
“En cadenas de HTML como esta:”
<a href="http://github.com">GitHub</a>
En pocas palabras, el primer elemento de la lista se convierte en el tipo de elemento HTML y el resto se convierte en el contenido de ese elemento. Opcionalmente, puede proporcionar un mapa de atributos que luego se adjuntará a ese elemento.
¡Los elementos se pueden anidar unos dentro de otros simplemente anidándolos dentro de la lista de sus padres! Esto se ve más fácilmente con un ejemplo:
[:div [:h1 "This is a header"] [:p "And in the next element we have 1 + 1"] [:p (+ 1 1)]]
Observe cómo podemos poner cualquier función antigua o sintaxis general de Clojure dentro de nuestra estructura sin tener que declarar explícitamente un método de incrustación. ¡Es solo una lista, después de todo!
Y aún mejor, ¿qué evalúa esto en tiempo de ejecución?
[:div [:h1 "This is a header"] [:p "And in the next element we have 1 + 1"] [:p 2]]
¡Una lista de palabras clave y contenidos, por supuesto! No hay tipos divertidos, ni métodos ocultos mágicos. Es simplemente una vieja lista de cosas. Puede unir y jugar con esta lista todo lo que quiera: lo que ve es lo que obtiene.

Con Hiccup haciendo el diseño y Reagent haciendo la lógica y el procesamiento de eventos, terminamos con un entorno React completamente funcional.
Un ejemplo más complicado
Muy bien, vinculemos esto un poco más con algunos componentes. Una de las cosas mágicas de React (y Reagent) es que compartimentas tu lógica de vista y diseño en módulos, que luego puedes reutilizar en toda tu aplicación.
Digamos que creamos un componente simple que muestra un botón y algo de lógica 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?")}]])
Nota rápida sobre la denominación: los módulos en Clojure normalmente tienen un espacio de nombres, por lo que widget.cljs
podría importarse bajo el widget
de espacio de nombres. Esto significa que se accederá a la función del component
de nivel superior como widget/component
. Me gusta tener solo un componente de nivel superior por módulo, pero esta es una preferencia de estilo: es posible que prefiera nombrar la función de su componente como polite-component
widget-component
.
Este componente simple nos presenta un widget educado opcional. (when polite? ", please.")
se evalúa como ", please."
cuando polite? == true
polite? == true
y nil
cuando es false
.
Ahora incrustemos esto dentro de nuestra app.cljs
:
(defn app [] [:div [:h1 "Welcome to my app"] [widget/component true]])
Aquí incrustamos nuestro widget dentro de nuestro componente de aplicación llamándolo como el primer elemento de una lista, ¡al igual que las palabras clave HTML! Luego podemos pasar cualquier elemento secundario o parámetro al componente proporcionándolos como otros elementos de la misma lista. Aquí simplemente pasamos true
, ¿así que en nuestro widget polite? == true
polite? == true
, y así obtenemos la versión educada.
Si tuviéramos que evaluar la función de nuestra aplicación ahora, obtendríamos lo siguiente:
[: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.
¡Observe cómo el widget/component
no ha sido evaluado! (Consulte la sección Funciones si está confundido).
Los componentes dentro de su árbol DOM solo se evalúan (y, por lo tanto, se convierten en objetos React reales detrás de escena) si se han actualizado, lo que mantiene las cosas agradables y ágiles y reduce la cantidad de complejidad con la que tiene que lidiar en cualquier momento.
Se pueden obtener más detalles sobre este tema para aquellos interesados en los documentos de reactivos.
Listas hasta el final
Además, observe cómo el DOM es solo una lista de listas y los componentes son solo funciones que devuelven listas de listas. ¿Por qué es esto tan importante mientras aprendes ClojureScript?
Porque todo lo que puede hacer con funciones o listas, puede hacerlo con componentes.
Aquí es donde comienza a obtener rendimientos compuestos mediante el uso de un dialecto Lisp como ClojureScript: ¡sus componentes y elementos HTML se convierten en objetos de primera clase que puede manipular como cualquier otro dato normal! Permítanme decir eso de nuevo:
¡Los componentes y los elementos HTML son objetos admitidos de primera clase dentro del lenguaje Clojure!
Así es, me escuchaste. Es casi como si Lisps estuviera diseñado para procesar listas (pista: lo estaban).
Esto incluye cosas como:
- Mapeo sobre elementos de una lista numerada:
(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"]]
- Envoltura de componentes:
; 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!
- Atributos de inyección:
(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"))]])
Tratar con tipos de datos simples y antiguos, como listas y mapas, es mucho más simple que cualquier cosa que se parezca a una clase, ¡y termina siendo mucho más poderoso a largo plazo!
Surge un patrón
Bien, recapitulemos. ¿Qué ha mostrado nuestro tutorial de ClojureScript hasta ahora?
- Todo se reduce a la funcionalidad más simple: los elementos son solo listas y los componentes son solo funciones que devuelven elementos.
- Debido a que los componentes y elementos son objetos de primera clase, podemos escribir más con menos .
Estos dos puntos encajan perfectamente en Clojure y el espíritu de programación funcional: el código son datos que se manipulan y la complejidad se construye conectando partes menos complejas. Presentamos nuestro programa (nuestra página web en este ejemplo) como datos (listas, funciones, mapas) y lo mantenemos así hasta el último momento cuando Reagent toma el control y lo convierte en código React. Esto hace que nuestro código sea reutilizable y, lo que es más importante, muy fácil de leer y comprender con muy poca magia.
ponerse elegante
Ahora sabemos cómo hacer una aplicación con alguna funcionalidad básica, así que pasemos a cómo podemos hacer que se vea bien. Hay un par de formas de abordar esto, la más simple es con hojas de estilo y haciendo referencia a sus clases en sus componentes:
.my-class { color: red; }
[:div {:class "my-class"} "Hello, world!"]
Esto hará exactamente lo que cabría esperar, presentándonos un hermoso y rojo "¡Hola, mundo!" texto.
Sin embargo, ¿por qué molestarse en colocar la vista y el código lógico en sus componentes y luego separar su estilo en una hoja de estilo? No solo tiene que buscar en dos lugares diferentes, sino que está tratando con dos idiomas también!
¿Por qué no escribir nuestro CSS como código en nuestros componentes (ver un tema aquí?). Esto nos dará un montón de ventajas:
- Todo lo que define un componente está en el mismo lugar.
- Los nombres de las clases pueden garantizarse como únicos mediante una generación inteligente.
- CSS puede ser dinámico, cambiando a medida que cambian nuestros datos.
Mi sabor favorito personal de CSS-in-code es con Clojure Style Sheets (cljss). El CSS incrustado tiene el siguiente aspecto:
;; -- STYLES ------------------------------------------------------------ (defstyles component-style [] {:color "red" :width "100%"}) ;; -- VIEW -------------------------------------------------------------- (defn component [] [:div {:class (component-style)} "Hello, world!"])
defstyles
crea una función que generará un nombre de clase único para nosotros (lo cual es excelente para cualquiera que importe nuestros componentes).
Hay muchas otras cosas que cljss puede hacer por usted (estilos de composición, animaciones, anulaciones de elementos, etc.) que no entraré en detalles aquí. ¡Te recomiendo que lo compruebes tú mismo!
Ensamblar las piezas de una aplicación ClojureScript
Por último, está el pegamento necesario para unir todo esto. Afortunadamente, además de un archivo de proyecto y un index.html
, el texto estándar es mínimo aquí.
Necesitas:
- Su archivo de definición de proyecto,
project.clj
. Un elemento básico de cualquier proyecto de Clojure, esto define sus dependencias, incluso directamente desde GitHub, y otras propiedades de compilación (similares abuild.gradle
opackage.json
). - Un
index.html
que actúa como punto de enlace para la aplicación Reagent. - Algún código de configuración para un entorno de desarrollo y, finalmente, para iniciar su aplicación Reagent.
Encontrará el ejemplo de código completo para este tutorial de ClojureScript disponible en GitHub.
Así que eso es todo (por ahora). Con suerte, al menos he despertado su curiosidad un poco, ya sea para comprobar un dialecto Lisp (Clojure [Script] o de otro tipo) o incluso para intentar crear su propia aplicación Reagent. Te prometo que no te arrepentirás.
Únase a mí en el seguimiento de este artículo, Getting Into a State, donde hablo sobre la gestión del estado mediante el reencuadre: ¡salude a Redux en ClojureScript!