Привет, Хабр!
Данный обзор познакомит вас с частичкой мира функциональных языков программирования, а именно с Elm, кто-то узнает про этот инструмент, кто-то про то, что фронтенд – это не только HTML, CSS и JS.
Elm – это функциональный язык программирования, который компилируется во всеми любимый javascript. Он обладает всеми плюсами и минусами этого семейства языков. Из самых важных особенностей стоит отметить, что язык является компилируемым. А также свойства языка принуждают вас сразу обдумать архитектуру вашего приложения: расписать, что попадёт в конкретную функцию, что из неё выйдет, и куда пойдёт дальше.
В данном материале я не буду углубляться в синтаксис языка, для этих целей существует множество гайдов (пусть и не так много, как в мейнстрим-языках).
Начну сразу с архитектуры приложений на Elm, которая показана на картинке ниже:
Elm генерирует HTML для отображения на экране, а затем приложение отправляет обратно сообщения (Msg) о каком-либо событии (например, нажатие кнопки).
Любое SPA (single page application) на Elm состоит из следующих элементов:
Init – начальное состояние модели.
Model — текущее состояние проекта.
View — отображение model.
Update — обновление состояния проекта на основе сообщений (Msg).
Что мы будем делать?
Для того что бы разобраться как работает Elm, было реализовано великое и могучее SPA под названием To-Do List (список дел) с возможностью записи дел и проведения некоторых операций над ними. Для демонстрации работы с запросами было добавлено соединение с БД (базой данных).
Наше SPA To-Do List будет обладать следующими возможностями:
создание элемента To-Do List’а;
редактирование (изменение названия и статуса выполнения задачи);
удаление;
сохранение в БД списка дел.
А реализовывать всё это мы будем с помощью следующих технологий:
Elm – виновник торжества.
Json-server и db.json в роли базы данных (БД).
Bootstrap – будет отвечать за красоту.
Начинаем с начала
Для того что бы использовать Elm, как бы это странно не звучало, необходимо установить Elm. Лучше скачивать с официального сайта (elm-lang.org). Так же на нём находится самая актуальная документация языка, которая по своей структуре напоминает обучающий курс. Можно скачать и с pypi, но пакет там не обновлялся с конца 2018 года.
После того как Elm установился, необходимо перейти в директорию проекта и запустить его с помощью команды:
elm reactor
Проект запустится на localhost:8000 и всё, достаточно изредка нажимать ctrl+s для сохранения и проект будет автоматически обновляться, не нужен даже LiveServer.
Архитектура приложения
Приложение состоит из 3-х модулей, каждый из которых вынесен в отдельный файл структура проекта показана на изображении ниже.
Main – основной модуль приложения, в котором происходит его инициализация, добавление в проект теги html, body и всех статичных элементов, а также вызов других модулей. Код данного модуля находится в файле с логичным названием Main.elm.
Main : Program Value Model Msg
Main =
Browser.document
{ init = =init,
, view = view
, update = update
, subscriptions = subscriptions
}
type Msg = NoOp | TodosMsg TodosMsg.Msg
type alias Model = {todos : List Todo, todoEditView : TodoEditView}
init : flags -> ( Model, Cmd Msg )
init fs =
let model = Model [] None
cmds = Cmd.batch[ Cmd.map TodosMsg Todos.Models.fetchAll ]
in ( model, cmds)
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
let
( newTodoEditView, newTodos, cmd ) = Todos.Models.update subMsg mod-el.todoEditView model.todos
newModel = { model | todoEditView = newTodoEditView, todos = newTodos }
in ( newModel, Cmd.map TodosMsg cmd )
view : Model -> Document Msg
view model =
{ title = "Туду"
, body =
[ div []
[
Html.map TodosMsg <| Todos.Models.viewEdit model.todoEditView
, br [] []
, Html.map TodosMsg <| Todos.Models.viewList model.todos
]
]
}
Models – модуль приложения, который отвечает за список дел. Из-за того, что пример небольшой, всё хранилось в одном месте. Но при более серьёзном проекте, хорошим тоном будет разделить код на разные модули. Здесь лежит таблица, которая хранит в себе дела, а также осуществлялись все действия с ними. Ниже приведена часть кода из данного модуля, который лежит в директории Todos и называется Models.elm.
-- Корпус таблицы
viewList : List Todo -> Html Msg
viewList todos =
Table.table{ options = [ Table.striped ]
, thead = Table.thead []
[ Table.tr []
[ Table.th [] [ text "Название" ]
, Table.th [] [ text "Готово" ]
, Table.th [] [
div [][ text "Действия" ]
, div[][ delCompl ]
]
]
]
, tbody = Table.tbody [] ( List.map todoRow todos )
}
-- Строки таблицы
todoRow : Todo -> Table.Row Msg
todoRow t =
let
{ id, title, completed } = table
( completedText, buttonText, buttonMsg ) =
if completed then ("Да", "Невыполненно", Revert )
else ("Нет", "Выполненно", Complete)
in
Table.tr []
[ Table.td [] [ text title ]
, Table.td [] [ text completedText ]
, Table.td [] [
Button [ onClick <| buttonMsg t ][ text buttonText ]
, Button [ onClick <| ShowEditView <| Editing t ][ text "Редактиро-вать" ]
, Button [ onClick <| Delete t ][ text "Удалить" ]
]
-- Функция удаления
delCompl : Html Msg
delCompl = Button[ onClick DeleteCompleted ][ text "Удалить выполненные" ]
Utils – модуль связи с БД. Здесь находится всё, что связано с данными, а именно: кодирование и декодирование json’а и запросы, для выполнения которых использовался пакет Http.request. Пример запроса показан ниже. Код вынесен из модуля Utils.elm.
--запрос для удаления
delete : a -> String -> Platform.Task Http.Error a
delete a url =
let decoder = Json.Decode.succeed a
request =
Http.request
{method = "DELETE"
, headers = []
, url = url
, body = Http.emptyBody
, expect = Http.expectJsopn decoder
, timeout = Maybe.Nothing
, withCredentials = False
}
in Http.toTask request
После реализации всех указанных модулей получится следующий результат:
Для тестового проекта, который никто не увидит – сойдёт. Но я делаю его для души, поэтому просто необходимо добавить немного красоты. И Elm в этом поможет, с помощью тесной интеграции с Bootstrap. Подключаем его следующей строкой в main:
import Bootstrap.CDN as CDN
А также импортируем bootstrap классы добавляя элиасы для простоты вызова:
import Bootstrap.Button as Button # и т.д. по необходимости
Благодаря bootstrap’у и небольшой доработке кода получается следующая картина:
При изменении названия, кнопка «Новая задача» заменяется на поля для редактирования наименования дела.
База данных
Для подъёма базы данных я использовал json-server, который был запущен на 4000 порту. Всего БД имеет 3 поля для каждого дела, а именно:
title <str> - название дела;
completed <boolean> - готовность дела;
id <int> - идентификатор дела.
Команда для запуска следующая:
Json-server db.json -p 4000
В итоге база данных выглядит следующим образом:
Что касается ответа на вопрос:
«Elm - забава или серьёзный инструмент?», он очень прост - всё зависит от вашего проекта. От себя скажу, что изначально программирование на Elm было похоже на поход в магазин на руках. Из-за того, что всё было непривычно, Elm требовал на разработку часы, когда с javascript на то же самое хватило бы и нескольких минут. Данное неудобство связано скорее с изначальным обучением программированию в императивном стиле и обычной привычкой.
Elm, как и множество функциональных языков программирования ломает ваш мозг, если раньше вы никогда не программировали в функциональном стиле, но взамен, он структурирует ваши мысли. Учит не создавать множество лишних переменных и функций, а также продумывать архитектуру приложения до того, как вы садитесь за написание кода.
Из минусов так же стоит отметить нераспространённость этого языка, и как следствие - отсутствие актуальной документации и примеров на русском языке, иногда даже на английском. Не всегда получается сразу найти информацию и приходится копать в дебри форумов.
Полный код приложения доступен по ссылке.