Desvendando o ClojureScript para desenvolvimento front-end

Publicados: 2022-03-11

Provavelmente, há dois pensamentos principais em sua cabeça enquanto você lê esta introdução:

  1. O que é ClojureScript?
  2. Isso não é relevante para os meus interesses.

Mas espere! É aí que você está errado - e eu vou provar isso para você. Se você estiver disposto a investir 10 minutos do seu tempo, mostrarei como o ClojureScript pode tornar a escrita de aplicativos front-end e React-y divertida, rápida e – o mais importante – funcional .

Alguns pré-requisitos do tutorial ClojureScript

  • Não é necessário conhecimento de Lisp. Farei o meu melhor para explicar quaisquer amostras de código que estejam espalhadas nesta postagem do blog!
  • No entanto, se você quiser fazer um pouco de pré-leitura, eu recomendo https://www.braveclojure.com/, o balcão único para começar com Clojure (e, por extensão, ClojureScript).
  • Clojure e ClojureScript compartilham uma linguagem comum - muitas vezes me referirei a eles ao mesmo tempo como Clojure[Script] .
  • Eu suponho que você tenha conhecimento de React e conhecimento geral de front-end.

Como aprender ClojureScript: a versão curta

Então você não tem muito tempo para aprender ClojureScript, e você só quer ver onde tudo isso está indo. Primeiramente, o que é ClojureScript?

Do site ClojureScript: ClojureScript é um compilador para Clojure que tem como alvo JavaScript. Ele emite código JavaScript que é compatível com o modo de compilação avançado do compilador de otimização do Google Closure.

Entre outras coisas, ClojureScript tem muito a oferecer:

  • É uma linguagem de programação multiparadigma com uma programação funcional enxuta — a programação funcional é conhecida por melhorar a legibilidade do código , além de ajudá-lo a escrever mais com menos código .
  • Ele suporta imutabilidade por padrão - diga adeus a todo um conjunto de problemas de tempo de execução!
  • É orientado a dados: código é dado em ClojureScript. A maioria dos aplicativos Clojure[Script] pode ser reduzido a um conjunto de funções que operam em alguma estrutura de dados subjacente, o que torna a depuração simples e o código superlegível.
  • É simples! Começar com ClojureScript é fácil - não há palavras-chave sofisticadas e muito pouca mágica.
  • Tem uma biblioteca padrão fantástica. Essa coisa tem tudo.

Com isso fora do caminho, vamos abrir esta lata de minhocas com um exemplo:

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

Nota para aqueles que não estão familiarizados com os dialetos Lisp ou ClojureScript: As partes mais importantes deste exemplo são o :div , o [] e o () . :div é uma palavra-chave que representa o elemento <div> . [] é um vetor, muito parecido com um ArrayList em Java, e () é uma sequência, muito parecido com um LinkedList . Vou abordar isso com mais detalhes mais adiante neste post!

Esta é a forma mais básica de um componente React no ClojureScript. É isso — apenas uma palavra-chave, uma string e um monte de listas.

Pshaw! você diz, isso não é significativamente diferente de “hello world” em JSX ou em TSX:

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

No entanto, existem algumas diferenças cruciais que podemos identificar mesmo neste exemplo básico:

  • Não há linguagens incorporadas; tudo dentro do exemplo ClojureScript é uma string, uma palavra-chave ou uma lista.
  • É conciso ; as listas fornecem toda a expressividade de que precisamos sem a redundância das tags de fechamento HTML.

Essas duas pequenas diferenças têm consequências enormes, não apenas em como você escreve código, mas também em como você se expressa!

Como é isso, você pergunta? Vamos entrar na briga e ver o que mais o ClojureScript tem reservado para nós…

Relacionado:
  • Introdução à linguagem de programação Elm
  • Introdução à linguagem de programação Elixir

Blocos de construção

Ao longo deste tutorial do ClojureScript, tentarei não me aprofundar muito no que torna o Clojure[Script] ótimo (o que é muitas coisas, mas discordo). No entanto, será útil ter alguns conceitos básicos abordados para que seja possível compreender a amplitude do que podemos fazer aqui.

Para aqueles experientes Clojuristas e Lispianos, sintam-se à vontade para pular para a próxima seção!

Existem três conceitos principais que precisarei abordar primeiro:

Palavras-chave

Clojure[Script] tem um conceito chamado Keyword. Está em algum lugar entre uma string constante (digamos, em Java) e uma chave. Eles são identificadores simbólicos que avaliam a si mesmos .

Por exemplo, a palavra-chave :cat sempre se referirá a :cat e nunca a qualquer outra coisa. Assim como em Java você pode dizer:

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

…em Clojure você simplesmente teria:

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

Observe também: no Clojure, um mapa é uma coleção (de chaves para valores, assim como um Java HashMap ) e uma função para acessar seu conteúdo. Arrumado!

Listas

Clojure[Script] sendo um dialeto Lisp significa que dá muita ênfase às listas. Como mencionei anteriormente, há duas coisas principais a serem observadas:

  1. [] é um vetor, muito parecido com um ArrayList .
  2. () é uma sequência, muito parecida com uma LinkedList .

Para construir uma lista de coisas no Clojure[Script], você faz o seguinte:

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

Para sequências, é um pouco diferente:

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

O prefixo ' é explicado na próxima seção.

Funções

Por fim, temos as funções. Uma função em Clojure[Script] é uma sequência que é digitada sem o prefixo ' . O primeiro elemento dessa lista é a própria função, e todos os elementos a seguir serão os argumentos . Por exemplo:

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

Um corolário desse comportamento é que você pode construir uma definição de uma função sem realmente executá-la! Apenas uma sequência 'nua' será executada quando o programa for avaliado.

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

Isso se tornará relevante mais tarde!

As funções podem ser definidas de algumas maneiras:

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

Um exame mais aprofundado

Agora que cobrimos o básico, vamos detalhar um pouco mais para ver o que está acontecendo aqui.

O React no ClojureScript geralmente é feito usando uma biblioteca chamada Reagent. Reagent usa Hiccup e sua sintaxe para representar HTML. Do wiki do repositório Hiccup:

“Hiccup transforma estruturas de dados Clojure assim:”

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

“Em strings de HTML como este:”

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

Simplificando, o primeiro elemento da lista se torna o tipo de elemento HTML e o restante se torna o conteúdo desse elemento. Opcionalmente, você pode fornecer um mapa de atributos que será anexado a esse elemento.

Os elementos podem ser aninhados entre si simplesmente aninhando-os na lista de seus pais! Isso é mais fácil de ver com um exemplo:

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

Observe como podemos colocar qualquer função antiga ou sintaxe geral do Clojure em nossa estrutura sem precisar declarar explicitamente um método de incorporação. Afinal, é apenas uma lista!

E ainda melhor, o que isso avalia em tempo de execução?

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

Uma lista de palavras-chave e conteúdos, é claro! Não há tipos engraçados, nem métodos mágicos ocultos. É apenas uma velha lista simples de coisas. Você pode emendar e brincar com esta lista o quanto quiser - o que você vê é o que está recebendo.

Com o Hiccup fazendo o layout e o Reagent fazendo a lógica e o processamento de eventos, acabamos com um ambiente React totalmente funcional.

Um exemplo mais complicado

Tudo bem, vamos amarrar isso um pouco mais com alguns componentes. Uma das coisas mágicas sobre React (e Reagent) é que você compartimenta sua visão e lógica de layout em módulos, que você pode reutilizar em toda a sua aplicação.

Digamos que criamos um componente simples que mostra um botão e alguma lógica simples:

 ; 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 nomenclatura: Módulos no Clojure normalmente são namespaced, então widget.cljs pode ser importado no widget namespace . Isso significa que a função do component de nível superior será acessada como widget/component . Eu gosto de ter apenas um componente de nível superior por módulo, mas isso é uma preferência de estilo - você pode preferir nomear sua função de componente como polite-component ou widget-component .

Este componente simples nos apresenta um widget opcionalmente educado. (when polite? ", please.") é avaliado como ", please." quando polite? == true polite? == true e para nil quando for false .

Agora vamos incorporar isso em nosso app.cljs :

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

Aqui, incorporamos nosso widget em nosso componente de aplicativo, chamando-o como o primeiro item de uma lista, assim como as palavras-chave HTML! Podemos então passar quaisquer filhos ou parâmetros para o componente fornecendo-os como outros elementos da mesma lista. Aqui nós simplesmente passamos true , então em nosso widget polite? == true polite? == true e, assim, obtemos a versão educada.

Se fôssemos avaliar nossa função de aplicativo agora, obteríamos o seguinte:

 [: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 como o widget/component não foi avaliado! (Consulte a seção Funções se estiver confuso.)

Componentes dentro de sua árvore DOM só são avaliados (e, portanto, convertidos em objetos React reais nos bastidores) se tiverem sido atualizados, o que mantém as coisas agradáveis ​​e rápidas e reduz a quantidade de complexidade com a qual você precisa lidar a qualquer momento.

Mais detalhes sobre este assunto para os interessados ​​podem ser obtidos na documentação do Reagente.

Listas até o fim

Além disso, observe como o DOM é apenas uma lista de listas e os componentes são apenas funções que retornam listas de listas. Por que isso é tão importante quando você aprende ClojureScript?

Porque qualquer coisa que você possa fazer com funções ou listas, você pode fazer com componentes.

É aqui que você começa a obter retornos compostos usando um dialeto Lisp como ClojureScript: Seus componentes e elementos HTML tornam-se objetos de primeira classe que você pode manipular como qualquer outro dado normal! Deixe-me apenas dizer isso novamente:

Componentes e elementos HTML são objetos suportados de primeira classe dentro da linguagem Clojure!

Isso mesmo, você me ouviu. É quase como se o Lips fosse projetado para processar listas (dica: eles eram.)

Isso inclui coisas como:

  • Mapeamento sobre elementos de uma 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"]]
  • Componentes de encapsulamento:
 ; 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!
  • Injetando atributos:
 (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"))]])

Lidar com tipos de dados simples e antigos, como listas e mapas, é dramaticamente mais simples do que qualquer coisa parecida com uma classe, e acaba sendo muito mais poderoso a longo prazo!

Surge um padrão

Ok, vamos recapitular. O que nosso tutorial do ClojureScript mostrou até agora?

  • Tudo é reduzido à funcionalidade mais simples – elementos são apenas listas e componentes são apenas funções que retornam elementos.
  • Como componentes e elementos são objetos de primeira classe, podemos escrever mais com menos .

Esses dois pontos se encaixam perfeitamente no Clojure e no ethos de programação funcional – código são dados a serem manipulados e a complexidade é construída por meio da conexão de partes menos complexas. Apresentamos nosso programa (nossa página da web neste exemplo) como dados (listas, funções, mapas) e o mantemos assim até o último momento em que o Reagent assume o controle e o transforma em código React. Isso torna nosso código reutilizável e, o mais importante, muito fácil de ler e entender com muito pouca mágica.

Ficando elegante

Agora sabemos como fazer um aplicativo com algumas funcionalidades básicas, então vamos ver como podemos fazer com que pareça bom. Existem algumas maneiras de abordar isso, sendo a mais simples com folhas de estilo e referenciando suas classes em seus componentes:

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

Isso fará exatamente como você esperaria, apresentando-nos um lindo e vermelho “Olá, mundo!” texto.

No entanto, por que ter todo esse problema para colocar a visualização e o código lógico em seus componentes, mas depois separar seu estilo em uma folha de estilo - não apenas agora você precisa procurar em dois lugares diferentes, mas também está lidando com dois línguas também!

Por que não escrever nosso CSS como código em nossos componentes (veja um tema aqui?). Isso nos dará um monte de vantagens:

  • Tudo o que define um componente está no mesmo lugar.
  • Os nomes de classe podem ser garantidos como exclusivos por meio de geração inteligente.
  • CSS pode ser dinâmico, mudando conforme nossos dados mudam.

Meu sabor favorito de CSS-in-code é com Clojure Style Sheets (cljss). O CSS incorporado se parece com o abaixo:

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

defstyles cria uma função que irá gerar um nome de classe exclusivo para nós (o que é ótimo para quem importa nossos componentes).

Há muitas outras coisas que o cljss pode fazer por você (estilos de composição, animações, substituições de elementos, etc.) que não entrarei em detalhes aqui. Eu recomendo que você verifique por si mesmo!

Montando as peças de um aplicativo ClojureScript

Por último, há a cola necessária para colar tudo isso junto. Felizmente, além de um arquivo de projeto e um index.html , o clichê é mínimo aqui.

Você precisa:

  • Seu arquivo de definição de projeto, project.clj . Um grampo de qualquer projeto Clojure, isso define suas dependências - mesmo diretamente do GitHub - e outras propriedades de compilação (semelhante a build.gradle ou package.json .)
  • Um index.html que atua como um ponto de ligação para o aplicativo Reagent.
  • Algum código de configuração para um ambiente de desenvolvimento e, finalmente, para iniciar seu aplicativo Reagent.

Você encontrará o exemplo de código completo para este tutorial ClojureScript disponível no GitHub.

Então é isso (por enquanto). Espero ter despertado um pouco a sua curiosidade, seja para verificar um dialeto Lisp (Clojure[Script] ou outro) ou mesmo para tentar fazer sua própria aplicação Reagent! Eu te prometo que você não vai se arrepender.

Junte-se a mim para acompanhar este artigo, Entrando em um estado, onde falo sobre gerenciamento de estado usando re-frame—diga olá ao Redux em ClojureScript!