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

Напомним - небольшой контекст

Образец для анатомирования решений

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

Это простое приложение с квадратной сеткой из 25 квадратов. Когда начинается новая игра, случайным образом выбираются 9 ячеек, которые отображаются пользователю на несколько секунд. После этого вопрос скрывается, и пользователь должен правильно выбрать 9 ячеек, щелкнув по ним еще раз. Когда пользователь выбирает 9 ячеек, мы сравниваем их с нашим вопросом. Если он получил все 9 ячеек правильно, ему присуждается 1 балл.

Это должно обеспечить некоторый контекст, когда мы проходим некоторый код.

Переходя к компонентам

Что, если бы каждая небольшая часть вашего приложения была приложением сама по себе?

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

Определение запасных частей нашего цикла

Что заставляет приложение Recall работать?

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

Из краткой биографии об игре и скриншота легко определить отдельные компоненты, над которыми мы можем начать работать:

Табло счета

Табло - простой компонент. Это просто, потому что в нем нет сложного журнала. Всякий раз, когда предоставляется оценка, она может отображаться. Модель приложения (которая имеет логику) определяет оценку. Каждый раз, когда результатом игры является выигрыш, счет должен быть увеличен на 1. Затем он может быть передан компоненту табло, который может его отобразить.

Мы не используем драйвер DOM в качестве источника, единственный источник - это поток оценок, за которые отвечает табло. Мы используем поток виртуальных узлов компонента со свойством dom в приемнике и визуализируем окончательный вид.

Новая игровая кнопка

Каждый раз, когда нажимается кнопка новой игры, создается новая головоломка. Но сама по себе новая игровая кнопка не может быть составной частью. Это не означало бы возможности для повторного использования. Что, если бы мы захотели изменить стиль или содержание? Вместо этого в качестве компонента мы можем использовать общий компонент кнопки, который мы можем использовать. Нам также нужно будет вернуть поток кликов по кнопке, чтобы другие потребители могли на него отреагировать.

Обратите внимание, что нам нужно было использовать изолировать. Это потому, что мы хотим изолировать наш dom-источник от этого компонента. Поскольку мы выбираем теги a, нам нужна изоляция, в противном случае мы улавливаем события клика всех тегов a в dom. Мы также параметризовали контент и селектор для настройки. Если нам понадобятся дополнительные параметры, мы можем добавить их в Источники кнопок позже. Мы также возвращаем поток событий кликов, чтобы другие могли подписаться на него.

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

Игровая сетка

Предыдущие 2 компонента практически не нуждались в логике. Однако мы видим, что у сетки может быть некоторая логика. В конце концов, он должен делать много вещей:

  • Каждый раз, когда создается новая головоломка, она должна отображаться на несколько секунд.
  • Затем пользователь должен иметь возможность выбрать / отменить выбор любой из 25 ячеек, щелкнув по ним.
  • Выбранные ячейки должны отображаться зеленым цветом.
  • Когда выбрано 9 ячеек, результат должен отображаться графически, с четким отображением правильных, неправильных и пропущенных ячеек.

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

Игровая ячейка

Мы сразу знаем, что игровая ячейка должна испускать поток событий щелчка. Он должен быть включен / отключен. Он также должен иметь некоторые состояния пользовательского интерфейса для отображения головоломки, поддержки отображения выбранных ячеек и отображения результатов. Начнем с состояний.

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

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

Игровая сетка - еще раз

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

Это очень много кода, поэтому мы подробно рассмотрим его фрагменты, чтобы понять всю идею. Есть вещи, которые можно сделать по-другому, это всего лишь один дубль.

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

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

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

reduce, has, remove и add - это служебные функции, которые мы написали, чтобы совместно использовать некоторые общие служебные программы. Здесь мы видим источник:

Теперь, когда у нас есть готовая логика, мы должны начать работу над созданием и использованием компонентов ячеек, и у нас должны быть готовы исходники. Мы уже создали массив чисел от 0 до 24 в константе с именем grid и пустой массив с именем ничего. Для каждого элемента в массиве grid нам нужно создать ячейку с этим индексом. Создадим ячейки. Но им нужны входы, поэтому давайте сначала создадим источники / входы для ячеек:

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

Состояние $ для ячейки зависит от индекса ячейки. Всякий раз, когда создается новая головоломка, состояние может быть выбрано или нормальное, в зависимости от того, включен ли щелчок по ячейке для выбора / отмены выбора. Когда он не включен, состояние ячейки зависит от результата. Но исходное состояние определяется головоломкой на 3 секунды. Это инкапсулируется в описанной ниже функции карты для массива cells.

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

Мы создаем cellClick $, сопоставляя каждое событие щелчка из ячейки с индексом ячейки и объединяя результирующие потоки из всех ячеек. Затем мы заставляем cellClickProxy $ имитировать действительный cellClick $, чтобы работала описанная выше логика для selected $. Осталось только скомбинировать домы и создать дом для сетки. Мы сделали это с помощью dom $. Мы просто возвращаем раковины.

Собираем наши запчасти в цикл

Использование компонентов в нашем приложении

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

Мы используем тот же механизм прокси-потока для подключения потоков. Следует отметить, что мы используем только прокси-потоки для подключения потоков действий, а не потоки состояний (в основном потому, что потоки состояний обычно запоминаются как потоки памяти, а потоки памяти нельзя имитировать предсказуемо ).

Используя эти новые данные, наша функция намерения изменяется следующим образом:

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

Наша модельная функция также будет освобождена от некоторых обязанностей, таких как забота о логике выбора. Теперь у нас есть более простое состояние, которое содержит головоломку $, результат $ и оценку $.

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

Что дальше?

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

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

ПРИМЕЧАНИЕ. Приложение для отзыва является живым и имеет связанный репозиторий на github. Обязательно оставьте свои предложения, отзывы и проблемы в разделе вопросов репо. Текущий снимок кода на момент написания может находиться здесь.

Что вы думаете? Мог бы я сделать что-нибудь лучше? Я что-то упустил? Поделитесь своими мыслями в комментариях к этому сообщению. Также дайте мне знать, есть ли в Cycle.js какие-то особенности, которые вам хотелось бы прочитать дальше.

Ваше здоровье!