Hoplon - это простой и мощный инструмент для создания веб-приложений из элементов с высокой степенью компоновки в ClojureScript.

Вам не обязательно понимать CLJS, чтобы прочитать этот пост, я написал его (надеюсь), чтобы он был доступен и для людей, использующих JavaScript!

Hoplon не полагается на взаимодействие с какой-либо базовой инфраструктурой JavaScript (например, React) - его ядро ​​представляет собой чистый CLJS с дополнительными расширениями для jQuery, Google Closure и т. Д. Основные функциональные возможности составляют менее 1000 LOC, поэтому чтение и понимание внутреннего кода приветствуются. и отличный способ изучить CLJS, если вы только начинаете!

Хоплон - это абстракция модели DOM.

Hoplon - это абстракция и расширение собственного JavaScript DOM API.

DOM API является родным для браузеров, объектно-ориентированным и изменяемым / сохраняющим состояние.

HTML-документы переносимы для совместного использования по сети между серверами / клиентами и, следовательно, не имеют состояния / неизменяемы.

Каждая структура, которая представляет разработчику что-то в стиле HTML (например, шаблоны JSX), внутренне устраняет разрыв между неизменяемыми данными HTML и изменяемыми объектами JS. Эти мосты могут быть простыми, легкими утилитами для создания шаблонов или невероятно монолитными и привязанными к поставщику.

У Hoplon нет логики перехода между HTML и DOM. У каждой функции Hoplon есть прямой и простой эквивалент JavaScript, продолжайте читать, чтобы увидеть примеры!

Нет виртуального DOM

Напишите газету, обещающую спасение, сделайте из нее что-то «структурированное» или «виртуальное»… и вы почти наверняка сможете создать новый культ.

Dijkstra

Современные мосты HTML в DOM представляют «виртуальную модель DOM», которая, по сути, представляет собой инструмент сравнения двух документов HTML, который может получать минимальный набор обновлений DOM для перехода от A к B Нам это нужно, потому что модель DOM не может быть дифференцирована и имеет ужасную производительность перед лицом избыточных обновлений.

Это очень быстро усложняется ...

В отличие от современников типа Hiccup / Reagent / etc. которые в первую очередь моделируют HTML, а затем соединяются с DOM через слой рендеринга, Hoplon напрямую нацеливается на DOM, чтобы предоставить:

Без процесса рендеринга нет виртуальной DOM.

Прямое создание элемента

Hoplon предоставляет набор функций для создания элементов DOM, например div. Поддерживаются все теги, если вы обнаружите, что какой-либо тег отсутствует, отправьте отчет об ошибке 😉.

(div)

Эквивалентно:

document.createElement('div');

Таким образом, мы не получаем ничего, кроме нового, обособленного div элемента в памяти.

Чисто и просто. 😀

Составные элементы

Hoplon расширяет тип js/Element до реализации Ifn.

Элементы или последовательность элементов, переданных как аргумент элементу, добавят аргументы как дочерние элементы.

(div (span))

Эквивалентно:

var div = document.createElement('div');
div.appendChild(document.createElement('span'));
return div;

Обратите внимание, что это не то же самое, что:

document.createElement('div').appendChild('span')

Поскольку appendChild() возвращает добавленный дочерний элемент, тогда как Hoplon возвращает родительский элемент. Для краткости представьте, что все дальнейшие примеры JS возвращают родительский объект вместо фактического результата вызовов связанных методов.

Поскольку возвращаемое значение - это родительский элемент, который может быть самим аргументом, вызовы вложенных функций выглядят устрашающе похожими на HTML, что полностью задумано.

(div (span) (div (span)))

Производит то же самое, что следующий HTML-код загружается в браузер:

"<div><span></span><div><span></span></div></div>"

Итак, мы уже начинаем видеть, как Hoplon, даже технически игнорируя HTML, эффективно устраняет разрыв между методами DOM и синтаксисом HTML.

Нам не нужен слой шаблонов поверх Hoplon, потому что возникающие структуры кода, которые Hoplon поощряет, уже отражают структуры кода HTML.

Добавление текста

Любые строковые или числовые аргументы преобразуются в текстовые узлы и добавляются.

(div "Hi!")

Эквивалентно:

document.createElement('div').appendChild(
  document.createTextNode('Hi!')
);

Получение / установка атрибутов

Пары ключ / значение в качестве аргументов элемента рассматриваются как атрибуты элемента.

(div :data-foo "bar")

Эквивалентно:

document.createElement('div').setAttribute('data-foo', 'bar');

Пока:

(div :data-foo nil)

Эквивалентно:

document.createElement('div').removeAttribute('data-foo');

Элементы также реализуют ILookup, поэтому ключевые слова можно использовать для получения атрибутов, как если бы элемент был хэш-картой своих атрибутов.

(:data-foo (div :data-foo "bar")

Возвращает "bar" и эквивалентно:

var el = document.createElement('div');
el.setAttribute('data-foo', 'bar');
return el.getAttribute('data-foo');

Добавление обработчиков событий

Если ключ - это имя события, тогда значение будет:

  • Запустить это событие, если правда
  • Привязать обработчик событий, если функция

Здесь стоит упомянуть, что у Hoplon есть альтернативные реализации в ядре, такие как jQuery и Google Closure.

Реализация jQuery особенно полезна при обсуждении событий, поскольку она нормализует логику до чего-то более простого для объяснения, чем собственная обработка событий:

(div :click true)

Эквивалентно (после требования hoplon.jquery):

jQuery(document.createElement('div')).trigger('click');

Пока

(div :click (fn [e] (.log js/console @e)))

Эквивалентно:

jQuery(document.createElement('div'))
  .on('click', function(e) {
    console.log(e.val());
  })
);

Обратите внимание, что для удобства Hoplon реализует IDeref для событий jQuery, так что @e в обработчике кликов становится e.val(). Это особенно удобно при работе с формами и другим пользовательским вводом. Необработанный объект события по-прежнему доступен как e.

Мутирующие элементы

Поскольку все js/Element объекты реализуют IFn, мы можем вызывать существующие элементы DOM как функции так же, как мы создаем новые элементы.

(let [el (div)]
  (el (span))
  (el :data-foo "bar"))

Эквивалентно:

let el = document.createElement('div');
el.appendChild(document.createElement('span'));
el.setAttribute('data-foo', 'bar');
return el;

И (div :data-foo "bar") то же самое, что ((div) :data-foo "bar").

Обратите внимание, что мы можем изменить любой элемент в любой момент времени, используя тот же синтаксис, что и при создании элемента Hoplon. Это отлично подходит для взаимодействия с другими библиотеками JS, поскольку Hoplon принимает любой элемент DOM.

Например, ((.getElementById js/document "#my-el") :data-foo "bar") полностью соответствует вашим ожиданиям 😀.

Синглтоны

До сих пор все работало против обособленной модели DOM или изменяло существующие элементы DOM.

Чтобы действительно добавить отдельные элементы на страницу, Hoplon реализует три «синглтона» - html, head и body. Вызов этих функций не создает новый элемент, а обновляет js/document свойства на месте.

(body (div))

Эквивалентно:

document.body.appendChild(document.createElement('div'));

Некоторые задачи загрузки (инструмент сборки), поставляемые с Hoplon, также могут компилировать файлы cljs, содержащие синглтоны, в HTML-страницу, которая загружает Hoplon, но это необязательно. Элементы Hoplon взаимодействуют с существующими системами, просто не используя синглтоны и загружая скомпилированный файл JS в обычном режиме.

Управление государством

В 2018 году многие из нас (я надеюсь) обменяли управление временными зависимостями (например, асинхронные обратные вызовы) на управление зависимостями данных (например, реактивные реквизиты). Если это так, мы не будем удовлетворены пропуском наших элементов через ад вложенных обратных вызовов только для того, чтобы в конце обновить какой-то эстетический атрибут.



Чтобы этого избежать, Hoplon имеет глубокую встроенную интеграцию с Javelin, другой библиотекой от создателей Hoplon.

Я не буду здесь слишком углубляться в Javelin, это совсем другой пост.

Javelin вводит концепцию «ячеек», которые очень похожи на собственные атомы cljs и поддерживают те же функции, reset!, swap! и т. Д.

Когда js/Element передается аргумент ячейки, он повторно запускает свою внутреннюю логику всякий раз, когда значение этой ячейки изменяется.

(let [c (cell "bar")]
 (div :data-foo c) ; <div data-foo="bar"></div> <-- create a div
 (reset! c "baz")) ; <div data-foo="baz"></div> <-- attribute update

Это означает, что мы можем записать всю логику нашего приложения в соответствии со значениями в наших ячейках, доверяя распространению значений на правильные элементы DOM.

Сами клетки можно передавать между элементами. Это простая кнопка, которая переключает видимость другого элемента:

(defn my-button [c] (button :click #(swap! c not)))
(defn my-el [c] (div :toggle c))
(div
 (let [c (j/cell true)] <-- same cell for both els...
  (div 
   (my-button c) ; <-- click this...
   (my-el c)))) ; <-- toggle this!

Ячейки реализуют модель реактивного программирования, в которой ячейки могут зависеть от значения других ячеек, используя cell= для «ячейки формулы», доступной только для чтения.

(let [a (cell "Hello")
      b (cell "world")
      c (cell= (str a " " b "!"))]
 (div c) ; <div>Hello world!</div>
 (reset! b "Dave")) ; <div>Hello Dave</div>

Каждая ячейка формулы имеет внутренний кеш, так что только новые значения накапливаются и распространяются. Когда мы где-то меняем несвязанную ячейку, нет риска забить DOM избыточными обновлениями. Это позволяет нам эффективно избежать необходимости в Virtual DOM.

Модульные тесты

Значения ячеек распространяются на элементы DOM, даже если они отсоединены, что упрощает написание модульных тестов для пользовательского интерфейса с помощью собственных инструментов cljs.

(deftest ??foo
 (let [c (cell "bar")
       el(div :data-foo c)]
  (is (= "bar" (:data-foo el))) ; passes!
  (reset! c "baz")
  (is (= "baz" (:data-foo el)))) ; passes!

Элементы высшего порядка

Поскольку элементы DOM являются функциями, мы можем использовать все собственные инструменты для работы с функциями CLJS.

Например, мы можем comp элемента вместе:

(def my-el (comp div span)) ; creates a fn that composes div/span
(my-el) ; <div><span></span></div>

Мы можем partial некоторые классы на то, что мы часто используем повторно:

(def big-button (partial button :class "big"))
(big-button "Click me!") ; <button class="big">Click me!</button>

Мы можем map по списку пунктов:

(map span ["Cat" "Dog"]) ; <span>Cat</span><span>Dog</span>

Расширяемость

Как я упоминал выше, Hoplon реализует базовую логику с помощью собственного JS, jQuery и Google Closure, а также обеспечивает поддержку SVG.

Все эти повторные реализации достигаются с помощью встроенных мультиметодов CLJS.

Вот как, например, Hoplon знает, что нужно рассматривать :click как событие, а не атрибут.

Эта расширяемость может быть действительно мощной, на днях я интегрировал Auth0, чтобы разрешить поддерживаемые JWT входы в социальные сети с одним атрибутом :login!.

(button :login! {:connection "google-oauth2"} "Login with Google")
(button :login! {:connection "facebook"} "Login with Facebook")


Это Гоплон!

Ну, по крайней мере, это все основы. Мне нужно узнать гораздо больше, но гораздо больше, чем я могу написать в одном посте ...

Надеюсь, я пробудил ваш интерес, присоединяйтесь к нам в #hoplon in clojurians Slack, чтобы пообщаться и узнать больше!

✉️ Подпишитесь на рассылку еженедельно Email Blast от CodeBurst 🐦 Подпишитесь на CodeBurst на Twitter , просмотрите 🗺️ Дорожная карта веб-разработчиков на 2018 год и 🕸️ Изучите веб-разработку с полным стеком .