С тех пор как я открыл для себя функциональное программирование, я знал, что могу создать приложение To-Do без написания собственных переменных. Я сделал несколько руководств и получил работающее приложение.

С тех пор как я открыл для себя программирование на основе автоматов, я знал, что могу создать приложение To-Do без написания собственных IF или логических значений. Ну теоретически. Раньше я этого не делал.

Я начал задаваться вопросом, смогу ли я создать приложение To-Do не только без переменных, но и без написания кода, похожего на фрагменты, показанные ниже.

if (isDone)
isDone ? ... : ...
value || defaultValue
showIf="model.isDone"
selected === "all"

Оказывается, с Росмаро это возможно!

В этом посте я собираюсь представить решения некоторых проблем, с которыми я столкнулся. Чтобы сделать этот пост кратким, я не собираюсь рассказывать об основах Rosmaro. Если вы хотите узнать об этом больше, посмотрите официальную документацию или другие мои посты, например, о реагировании и программировании на основе визуальных автоматов.

Весь код безбуквенного приложения To-Do находится на GitHub. Вы также найдете там демо.

Главный узел - граф

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

Мы вернемся к коду, обрабатывающему главный узел, в конце, потому что необходимо знать, как отрисовываются Items и Navigation.

Форма - график и код

Форма простая. Он не отличается от описанного в статье Реагировать и программировать на основе визуальных автоматов.

Он построен с использованием двух узлов:

  • граф под названием NewItemForm
  • лист с именем NewItemFormView

Граф NewItemForm существует только для обработки циклов от NewItemFormView до NewItemFormView. Это позволяет обновлять контекст, когда пользователь вводит содержимое нового элемента или добавляет новый элемент.

Вот код представления:

Как мы видим, этот обработчик работает с контекстом, имеющим следующую форму:

Очень важно отметить, что новые элементы всегда добавляются в конец массива. Таким образом индекс элемента никогда не меняется. Если он был добавлен на третьей позиции, он останется на третьей позиции. Обещаю, что скоро станет ясно, зачем это нужно.

Как работает навигация без логических значений?

ИФ нет. Нет никаких фильтров. Как тогда это работает?

В нем задействованы три узла:

  • Навигация
  • Предметы
  • главный

Когда Items визуализируются, они создают такой массив:

Каждому элементу этого массива соответствует одна задача.

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

Но как узнать, какой ключ должен отображаться?

Когда отображается Navigation, возвращается не только представление. Он возвращает структуру такой формы:

{UI, show}

Под клавишей UI находится представление. Значение под клавишей show - это функция карты.

Общий предок Items и Navigation, то есть главный узел, передает результат функции рендеринга Items в show , возвращаемая узлом Navigation.

Как меняются кнопки навигации?

Навигация может находиться в одном из следующих состояний:

  • показаны все предметы
  • показаны только выполненные работы
  • отображение только отмененных элементов

Все они могут быть дочерними элементами одного графа. Единственная проблема - переходы. Каждый узел должен быть связан со всеми остальными узлами. Чтобы ограничить количество требуемых стрелок, я использовал пользовательские точки входа и петли.

Вот как выглядит узел Navigation:

А вот вид:

Каждый раз, когда пользователь нажимает кнопку Отменить, он делает текущий узел представления (Все, Готово или Отменить ) следуйте за стрелкой перехода к отмене.

Поскольку такой стрелки на графике NavigationView нет, он заставляет локальный узел Navigation View следовать за этой стрелкой. Это похоже на всплытие событий.

Когда узел Просмотр следует за стрелкой с названием переход к отмене, он создает цикл, проходящий через точку входа отменено.

Когда график Просмотр вводится через точку входа Отменить, дочерний элемент Отменить становится активным узлом.

Таким образом мы сможем избежать взрыва перехода при добавлении другого состояния навигации. Всегда потребуется не более 2-х стрелок.

Каждый обработчик закодирован так:

Это сделано? График подскажет!

Нет логического значения, указывающего, выполнено ли задание. Вместо этого это выражается нарисованным графиком.

Мы ясно видим, что новый элемент не сделан. Щелчок по нему отмечает это как выполненное. Повторное нажатие помечает его как отмененное.

Обработчики листьев используют функцию, о которой еще не упоминалось, - вызов методов отдельного узла вместо всей модели.

Используя thisModelNode вместо thisModel, мы гарантируем, что щелчок по элементу вызывает метод clickItem этого самого элемента, а не его братьев и сестер.

Здесь мы также можем видеть, что когда навигация запрашивает выполненные элементы, отмененный элемент не отображается (вместо его представления отображается null).

Как создается список предметов?

Выше мы видели, что отдельный элемент - это график. Он также ожидает такого контекста:

{title, id}

Но задач может быть много, а не один. И контекст всей модели не выглядит таким.

Как это работает? В нем используются динамические композиции и контекстные линзы. Вы можете найти более подробную информацию о динамических композитах в документации Росмаро.

Узел Items - это динамический составной элемент, в котором в качестве шаблона используется граф Item.

Обработчик Items предоставляет функцию контекста, которая возвращает список его дочерних элементов. В этом случае они названы в честь индексов массива, в котором хранятся все элементы, которые нужно сделать. Для целей этого простого приложения To-Do эти индексы служат уникальными идентификаторами элементов. Вот почему так важно всегда добавлять новые элементы в конец массива - иначе их идентификаторы изменились бы.

Поскольку мы хотим видеть недавние элементы вверху списка, мы обращаем результат рендеринга всех элементов.

Хорошо, вот как создается список. Но как насчет контекста? Он формируется линзой, прикрепленной к обработчику диаграммы Item.

Под клавишей ctxLens находится заводская функция линзы Ramda. Фабрика берет локальное имя узла. В данном случае это индекс элемента. Мы знаем это, потому что именно этим и занимается функция nodes обработчика узлов Items.

Поскольку узлы Item только читают контекст и никогда не вызывают переходы, которые могли бы его обновить, единственная часть линзы, которая действительно имеет значение, - это та, которая увеличивает масштаб контекста. Вот она:

Итак, когда localNodeName равно «1» и контекст выглядит так:

{
  items: ['first', 'second', 'third']
}

Узел Item будет видеть такой контекст:

{
  id: "1", // because node names are always strings based on array indexes starting at 0
  title: "second"
}

Заключение

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

Спасибо за чтение этого!