Понимание стека и построение основы проекта

Я познакомился с 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!

Увидимся! ✌🏽

использованная литература