В прошлой статье я сравнивал HappyX и Karax, показывая их декларативный подход к разработке с помощью Nim.
В этой статье я подробнее расскажу о разработке одностраничных приложений с помощью HappyX с использованием императивного подхода к разработке.
Так как в прошлой статье сравнивался лишь сам код, я начну с того, как установить HappyX. Для начала необходимо поставить сам Nim, если он у вас стоит — отлично.
Следующий шаг — установка HappyX с помощью пакетного менеджера nimble. Ставим последнюю версию:
nimble install happyx@#head
Отлично, можно проверить установленную версию с помощью
hpx -v
При желании можно поставить любую другую версию, например для установки версии
3.5.2
нужно ввести командуnimble install happyx@3.5.2
Теперь можно переходить к созданию проекта. Для этого достаточно перейти в нужную вам директорию и ввести следующую команду.
hpx create --kind HPX --name project --use-tailwind
--kind HPX
— выбор типа проекта, в данном случае мы выбрали одностраничное приложение с поддержкой.hpx
файлов.--name project
— выбор имени для проекта.--use-tailwind
— для одностраничного приложения мы также можем включить поддержку Tailwind CSS 3.
В созданном проекте можно наблюдать файл src/main.hpx
(в исходный код добавлены комментарии):
<template>
<div>
<!-- Использование компонента HelloWorld -->
<!--
Передача параметров:
userId как целое число,
query и pathParam как строки
-->
<HelloWorld userId:int="10" query="meow" pathParam="Path Param Example"></HelloWorld>
</div>
</template>
Также в файле src/components/HelloWorld.hpx
можем видеть следующее:
<template>
<div>
Hello, world! {self.userId} <!-- Подстановка значений из текущего компонента -->
<p>
Query is
{self.query}
</p>
<p>
pathParam is
{self.pathParam}
</p>
</div>
</template>
<!--
Объявление скрипта, по желанию можно использовать здесь JavaScript,
указав lang="javascript"
-->
<script>
# Здесь можно писать на Nim
echo "Hello, world!"
# Здесь задаются пропсы компонента, которые можно передавать из других компонентов
# Пропсам также можно задавать значение по умолчанию, как написано ниже
props:
userId: int = 0
query: string = ""
pathParam: string = ""
</script>
<!-- Изолированная область стилей, применяемая только для данного компонента -->
<style>
.div {
border-radius: 4px;
background-color: #212121;
color: #ffffff;
}
</style>
Помимо .hpx файлов в папке src находятся index.html
и router.json
. В первом нет ничего интересного кроме подключения скомпилированного js
файла, а вот со вторым разберемся подробнее:
{
"/": "main.hpx",
"/user{ARG1:int}/{ARG2?:string}": {
"component": "HelloWorld",
"args": {
"userId": "ARG1",
"query": {
"name": "q",
"type": "query"
},
"pathParam": {
"name": "ARG2",
"type": "path"
}
}
}
}
В файле router.json
описаны маршруты вашего приложения, здесь вы можете изменять маршруты, задавать точки входа компонентов а также задавать параметры к маршрутам (query
либо path
).
Для того, чтобы запустить проект, достаточно находиться в папке с проектом (cd project
). Теперь вы сможете наблюдать все изменения файлов в реальном времени.
hpx dev --reload
Попробуем изменить src/main.hpx
следующим образом и сохранить файл:
<template>
<div>
<h1 h-if="self.showTitle()">
Добро пожаловать!
</h1>
<h1 h-else>
Скрытый заголовок
</h1>
</div>
</template>
<script>
<!-- Объявим метод компонента - showTitle -->
proc showTitle(): bool =
true
</script>
В <template></template>
можем наблюдать применение директив h-if
и h-else
, некие аналоги v-if
и v-else
из Vue.js. В <script></script>
находится объявление метода showTitle
, который возвращает true
.
Как можно видеть - отображается лишь "Добро пожаловать!". Это происходит потому, что метод showTitle()
всегда возвращает true
.
Попробуем изменить скрипт:
<script>
import random
randomize()
proc showTitle(): bool =
rand(100) > 50
</script>
Здесь мы добавили импорт стандартной библиотеки Nim — random, далее единожды вызываем функцию randomize()
, теперь метод showTitle
будет возвращать случайное значение. Попробуйте перезагрузить страницу несколько раз. Каждый раз надпись меняется!
Теперь попробуем реализовать собственный компонент, который будем использовать в src/main.hpx
. Пусть этот компонент будет обычным счетчиком. Создадим файл src/components/Counter.hpx
и запишем в него следующее:
<template>
<div class="flex justify-around rounded-md border-2 border-gray-900 bg-gray-300 px-2 w-24">
<div class="flex w-full self-center">{self.count}</div>
<div class="flex flex-col text-sm">
<button h-onclick="self.increase()">????</button>
<button h-onclick="self.decrease()">????</button>
</div>
</div>
</template>
<script>
# Объявляем пропсы
props:
count: int = 0
# Метод для увеличения счетчика
proc increase() =
self.count += 1
# Метод для уменьшения счетчика
proc decrease() =
self.count -= 1
</script>
В этом компоненте мы используем новую директиву — h-onclick
. С помощью нее мы можем обращаться к методам либо писать какой‑то короткий Nim код.
Теперь обновим файл src/main.hpx
, чтобы использовать в нем наш счетчик:
<template>
<div>
<h1 h-if="self.showTitle()">
Добро пожаловать!
</h1>
<h1 h-else>
Скрытый заголовок
</h1>
<div class="p-2">
<Counter></Counter>
</div>
</div>
</template>
Как вы видите - достаточно лишь использовать тег <Counter></Counter>, чтобы использовать наш компонент. Также мы можем передать в него начальное значение счетчика:
<div class="p-2">
<!--
При передаче указываем, что это целое число,
так как count в компоненте - целое число
-->
<Counter count:int="5"></Counter>
</div>
Ниже можно видеть результат, который у нас получился:
Добавим также верхний и нижний порог счетчика:
<script>
# Объявляем пропсы
props:
count: int = 0
floor: int = int.low
ceil: int = int.high
# Метод для увеличения счетчика
proc increase() =
if self.ceil <= self.count:
return
self.count += 1
# Метод для уменьшения счетчика
proc decrease() =
if self.floor >= self.count:
return
self.count -= 1
</script>
Здесь мы добавили два пропса: ceil
и floor
, чтобы задать верхний и нижний порог соответственно. В начале методов мы добавили проверку, которая остановит функцию в случае достижения нижнего или верхнего порога.
Посмотрим, как это выглядит в src/main.hpx
:
<div class="p-2">
<Counter count:int="5" floor:nim="0" ceil:nim="10"></Counter>
</div>
Тут мы добавили передачу порогов с указанием :nim
, с помощью которого тип автоматически распознается.
Маршрутизация
Теперь перейдем к маршрутизации, и, возвращаясь к src/router.json
сразу добавим новый маршрут, по которому будет доступен только компонент с нашим счетчиком, при это мы сможем передавать ему значения.
{
"/": "main.hpx",
"/user{ARG1:int}/{ARG2?:string}": {
"component": "HelloWorld",
"args": {
"userId": "ARG1",
"query": {
"name": "q",
"type": "query"
},
"pathParam": {
"name": "ARG2",
"type": "path"
}
}
},
"/counter/{value:int}": {
"component": "Counter",
"args": {
"count": "value"
}
}
}
Теперь попробуем зайти на http://localhost:5000/#/counter/10:
Попробуйте поиграться с адресной строкой — значение меняется.
Теперь добавим маршрут, в котором можно регулировать порог счетчика:
"/counter/{value:int}": {
"component": "Counter",
"args": {
"count": "value"
}
},
"/counter/{value:int}/{floor:int}-{ceil:int}": {
"component": "Counter",
"args": {
"count": "value",
"floor": "floor",
"ceil": "ceil"
}
}
Таким образом можно настраивать маршруты любой сложности и взаимодействовать с ними.
Директивы
В примерах выше уже были использованы директивы h-if
и h-else
, однако кроме них также есть ряд директив.
h-if
, h-elif
, h-else
служат для условного рендеринга:
<template>
<h1 h-if="5 > 10">5 больше 10</h1>
<h1 h-elif="5 == 10">5 = 10</h1>
<h1 h-else>5 меньше 10</h1>
</template>
директива h-for
служит для цикличного рендеринга:
<template>
<div>
<div h-for="i in [1, 2, 3, 4]">
{i}
<div h-for="j in i..10">
{i + j}
</div>
</div>
</div>
</template>
Помимо h-for
для цикличного рендеринга может использоваться h-while
:
<template>
<script lang="nim">
# Здесь мы пишем на Nim
var x = 1
</script>
<div class="flex">
<div h-while="x <= 3">
{x}
<script lang="nim">
# Здесь мы пишем на Nim
x += 1
</script>
</div>
</div>
</template>
Также существуют директивы для обработки событий. Они начинаются с h-on
, например
h-onclick
- обрабатывает нажатие мыши на элемент (click event);h-onmouseover
- срабатывает при наведении мыши на элемент (mouseover event);h-onwheel
- срабатывает при прокрутке колесика мыши (wheel event).
В каждом обработчике событий можно отправлять Event.
<template>
<div>
<button h-onclick="echo event.MouseEvent.x" h-onmouseover="echo event.MouseEvent.x">
Click!
</button>
<input placeholder="edit text ..." h-oninput="echo event.target.value" />
</div>
</template>
Заключение
Императивный подход к разработке в HappyX все еще находится в разработке, однако уже предоставляет некоторый функционал, в том числе маршрутизацию, директивы для отрисовки элементов, компонентный подход а также обработку событий. При этом Nim используется как основной язык для разработки.
Ссылки
Прошлая статья о сравнении HappyX и Karax.
GitHub репозиторий.
Официальный сайт с документацией, написанный на HappyX.
hardtop
Выглядит интересно. Но не могу понять, в чём изюминка. У ребят на js и так тьма своих фреймворков. Может, рассматривать как дополнение к python? Синтаксис у Нима схож, быстрый...
akihayase Автор
изюминка заключается в том, что вы можете разрабатывать одностраничные сайты не на JS, а на Nim :)
hardtop
Возможность избежать JS, конечно выглядит заманчивой. Но полностью отказаться от него сложно, потому как Листалки нужны, Анимации gsap, Графики динамические. Мы ж не будем переписывать - а просто возмём готовое.
Хотя возможность не использовать Реакт - безусловное добро!
akihayase Автор
Отчасти согласен с вами, однако есть две вещи, которые позволяет Nim:
1. Делать JS вставки напрямую в код, который выйдет после компиляции
2. Взаимодействовать с JS напрямую через стандартную библиотеку jsffi, таким образом позволяя разработчику обращаться к любому JS объекту - вызывать функцию/метод, обращаться к переменной и так далее.
Таким образом, вы можете использовать JavaScript, при этом остаетесь на Nim :)
UPD:
Далеко ходить не надо, ниже пример из официальной документации по JSFFI: