Понимание стека и построение основы проекта
Я познакомился с Clojure много лет назад, когда познакомился с технологиями, лежащими в основе Nubank. Несмотря на то, что в то время у меня был несколько лет опыта, до этого дня я никогда не работал и не видел каких-либо языков Lisp. Он привлек меня своим изначально запутанным, но все же красивым синтаксисом, который по своей сути излучает простоту.
Однако с тех пор я только проходил быстрые курсы по Clojure и смотрел тонны выступлений и видео на YouTube. Кроме того, отсутствие хороших руководств и документации по разработке приложений мешает кому-то начать копаться в экосистеме. Из-за этого я никогда не пробовал применить это в проекте.
Что ж, до сих пор.
Пару недель назад я решил глубже погрузиться в экосистему Clojure и создать приложение с нуля. Эта статья - первая часть моих приключений в океане скобок Clojure (Script).
Приложение
Принимая во внимание тот факт, что я собираюсь изучать новый язык, рабочие процессы и фреймворки, я решил, что было бы лучше использовать проект, по крайней мере, с уже созданным прототипом некоторого базового пользовательского интерфейса; и, таким образом, я мог сосредоточиться на самом важном.
В прошлом году я прошел учебный курс по Node.js / ReactJS / React Native от Rocketseat, и одним из проектов, над которым мы работали, была система управления финансами под названием GoFinance. Это очень просто и красиво, и мне идеально подходит для переноса на ClojureScript.
При этом я изменил название, изменил цветовую палитру и добавил некоторые дополнительные функции. GoBudget родился:
Идея в том, чтобы быть очень простой. Когда вы добавляете новые транзакции: они помещаются в список; все значения пересчитываются; и жизнь продолжается. Ничего сложного для первого проекта ClojureScript.
Он также содержит простой профиль, страницы регистрации и входа.
Стек
Как пользователь React, это решение было довольно простым: я решил использовать Reagent + Re-frame. Реагент, как описано на его странице на github:
'… [] минималистичный интерфейс между ClojureScript и React. Он позволяет вам определять эффективные компоненты React, используя только простые функции и данные ClojureScript, которые описывают ваш пользовательский интерфейс с использованием синтаксиса, подобного Hiccup.
Цель Reagent - дать возможность определять произвольно сложные пользовательские интерфейсы, используя всего несколько базовых концепций, и быть достаточно быстрыми по умолчанию, чтобы вам редко приходилось думать о производительности.
Оказывается, что иногда, поскольку ClojureScript использует замечательный компилятор Google Closure, проект Reagent может быть меньше и быстрее, чем сам React. 🚀
Чтобы не забыть переделать кадр:
'..это фреймворк ClojureScript для создания пользовательских интерфейсов. Он имеет функциональный дизайн, ориентированный на данные. Его основное внимание уделяется высокой производительности программистов и масштабированию до более крупных одностраничных приложений.
Хотя при повторном фрейме используется React (через Reagent), для этого нужно, чтобы React был только V в MVC, и не более того. re-frame - это другой путь к широко распространенной в настоящее время идее о том, что представления должны быть причинными (размещенные запросы, ComponentDidMount, хуки и т. д.). При изменении кадра события являются причинными, а просмотры - чисто реактивными. '
Итак, речь идет о data
и functions
, которые преобразуют эти данные. И поскольку это реактивный фреймворк, data
координирует functions
, а не наоборот.
Слезы радости текут из моих глаз 🥳
ClojureScript SPA 101
Я должен сказать, что, поскольку я некоторое время флиртовал с Clojure, у меня действительно есть некоторые краткие знания о его основах; и поскольку вокруг языка Clojure (Script) есть очень хорошие ресурсы, я сосредоточусь здесь на знаниях, необходимых для понимания того, как создать одностраничное приложение с ClojureScript, Reagent и перефразировать.
Реагент
Точно так же, как React имеет Jsx, который является расширением синтаксиса для Javascript, который позволяет нам писать html в Javascript, Reagent использует Hiccup. Hiccup - это библиотека для представления HTML в Clojure. Он использует векторы для представления элементов и карты для представления атрибутов элемента.
Основное отличие от React Jsx заключается в том, что Hiccup использует собственные структуры данных Clojure для представления компонента:
Это просто вектор Clojure, определяющий html-тег a
как ключевое слово, при использовании карты Clojure для настройки его свойств (в данном случае href
и onClick
).
Точно так же, как вы можете составлять html-теги для формирования более сложного элемента, вы можете сделать это с помощью Hiccup:
Вы даже можете добавить некоторые встроенные стили или использовать классы, чтобы стилизовать их позже с помощью css:
Есть два способа определения класса: вы можете использовать синтаксический сахар, который предоставляет нам Hiccup, :a.profile-avatar-link
, или использовать ключевое слово :class
; тем не менее, последнее лучше для динамического определения классов. То же самое происходит со свойством id: :a#profile-avatar-link
создаст HTML-тег a
с идентификатором profile-avatar-link.
.
Никаких новых языков, шаблонов или смешивания различных структур для создания элемента. Все дело в объединении собственных структур данных Clojure. Есть более подробная информация о настройке стилей и компонентов, но я вернусь к ней, когда придет время.
В дополнение к этому, например, для простого состояния формы нам вообще не нужно использовать повторное кадрирование. Вместо этого мы можем использовать собственную версию atom
от Reagent. Он работает аналогично Clojure с добавлением повторного рендеринга компонента при изменении этого атома:
Легкий лимонный отжим.
Состояние и переделка
Одна из важных частей приложения - это его состояние и то, как мы им управляем. В течение многих лет разработчики React сохраняли состояние каждого компонента, в значительной степени основанного на объектно-ориентированном программировании, из-за чего состояние приложения было разбросано повсюду.
Затем появился Redux, сделавший состояние глобальным и управляющий им с помощью действий.
Оказывается, идея Redux на самом деле возникла из-за повторного кадра, и это то, что я собираюсь использовать для управления состоянием нашего приложения.
Re-frame работает через события и подписки. Всякий раз, когда нам нужно изменить состояние нашего приложения, мы отправляем какое-то событие, чтобы сделать это за нас. Итак, если база данных приложения выглядит просто так:
И у нас есть обработчики событий и подписчики, как показано ниже:
Затем мы можем отправлять события и подписываться на изменения в нашей базе данных:
Событие :create-transaction
обновляет данные :transaction-history
в состоянии нашего приложения. Тот, кто подписан на эту конкретную информацию в состоянии, уведомляется о событии, а затем Reagent повторно визуализирует этот компонент.
Теперь каждый компонент в нашем приложении является реактивным.
Настройка локального хоста
Ну, это было действительно сложно. Есть много вариантов, с которых вы можете начать. Святой Грааль разработки Clojure - это Emacs. Меня, как человека, только начинающего работать в мире Clojure, это до чертиков пугает. Я никогда в жизни не использовал Emacs, тогда изучение нового инструмента было бы обременительным.
К счастью, есть отличные альтернативы! В моем случае, будучи пользователем VSCode, я решил придерживаться его и добавил несколько хороших расширений:
- Calva, интегрированный REPL. Он включает встроенную оценку кода, Paredit, форматирование кода, средство запуска тестов, подсветку синтаксиса, линтинг и многое другое;
- Clojure Lint: это расширение связывает код Clojure и EDN с помощью clj-kondo. Линтинг будет происходить автоматически при сохранении файла Clojure.
- Color Highlight: ну, мы займемся стилизацией, так что нет ничего лучше, чем расширение, которое поможет нам в этом. Это расширение стилизует CSS / веб-цвета в вашем документе.
Кроме того, мне нужно было установить Java JDK и Node.js (и, следовательно, npm). Я установил последнюю стабильную версию openjdk с помощью sdkman, что очень рекомендую; и выпуск LTS узла через nvm.
Чуть не забыл главную звезду шоу: Clojure! Давайте установим инструменты CLI, запустив:
$ brew install clojure/tools/clojure
PS: Я использую Mac OS. Если вы хотите установить его и используете другую систему, перейдите по ссылке здесь.
Все настроено, пора начать пачкать руки в коде.
Настройка проекта
Когда я создавал приложение shadow-cljs, для нас есть команда npm для начальной загрузки проекта:
$ npx create-cljs-app gobudget-web
После этого у меня есть структура проекта, которая выглядит так:
Стандартный код помогает понять, как работает Reagent, поэтому стоит взглянуть на него. Я пошел дальше и начал очищать проект.
Уборка вещей вверх
Я удалил все файлы, кроме core.cljs
в папке app
; Я тоже удалил папку e2e
. Таким образом, я мог начать с почти пустого проекта.
Внутри core.cljs
я удалил кое-что ненужное, поэтому в итоге получаю такой файл:
Здесь стоит упомянуть пару вещей:
^:export
- это метаданные, которые делают основную функцию доступной для движка javascript. Таким образом приложение можно будет визуализировать из файлаindex.html
.^:dev/after-load
- это метаданные, которые shadow-cljs использует в качестве ловушки для обеспечения функций горячей перезагрузки нашего приложения.(:require [reagent.core :as r])
включает реагент в пространство имен ядра, поэтому мы можем вызватьr/render
для рендеринга нашего приложения в DOM.[]
в нашемr/render
вызове будет содержать наш корневой компонент, когда мы его создадим. Таким образом, реагент может внедрить наше приложение в DOM.
(r/render [app] (.getElementById js/document "app"))
в значительной степени то же самое, что:
ReactDOM.render( <App /> ,document.getElementById('app'), )
Кроме того, я создал файл db.cljs
под папкой приложения. Этот файл будет содержать состояние нашего приложения, которое в дальнейшем будет управляться с помощью событий повторного кадра. Исходная база данных будет содержать некоторые данные, поэтому мы можем визуализировать их в браузере; а пока давайте просто определим его основные ключи:
Давайте передадим компонент в функцию r/render
Reagent, чтобы мы наконец кое-что увидели в браузере! Создайте новый файл с именем main.cljs
со следующим содержимым:
Совершенно ясно, что делает этот файл:
- Определение пространства имен
app.main
; - Создание функции с именем root, которая является нашим первым компонентом. Он возвращает тег
<h1>
с текстом выше. Круто, да?
Теперь новый текст нужно отобразить в браузере. Для этого я импортировал пространство имен app.main
в core.cljs
, ссылаясь на наш компонент с помощью ключевого слова :refer
. Затем я передал корневой компонент в качестве параметра функции r/render
:
Запуск npm start
в терминале запускает команду shaddow-cljs watch app
; он создает наше приложение, запускает приложение и продолжает следить за нашими файлами. Таким образом, всякий раз, когда я вносил изменения, наше приложение автоматически перезагружалось.
При доступе к http://localhost:3000/
в браузере должен отображаться текст:
Изменение текста запускает shadow-cljs для автоматической сборки приложений и перезагрузки их в браузере (F5 не требуется!).
[:app] Compiling ... [:app] Build completed. (154 files, 2 compiled, 0 warnings, 0.22s)
Подготовка к тому, что впереди
Теперь, когда проект настроен и работает, следующим шагом будет создание приложения. Я собираюсь создать несколько базовых страниц для нашего приложения, а также расскажу о структуре папок проекта ClojureScript: что я видел вокруг и чего я решил придерживаться.
Я также собираюсь провести несколько первых экспериментов со стилями с использованием как CSS, так и встроенных стилей с помощью Hiccup. Впереди еще много интересного!
Спасибо, что попали сюда! Увидимся в следующей главе моего приключения по ClojureScript!
Увидимся! ✌🏽