Создание решений по управлению состоянием для редакторов Creative Tools

Nov 28 2022
Введение В этой статье мы поделимся некоторыми из наших (слегка самоуверенных) идей по проектированию и созданию масштабируемых решений для управления состоянием в контексте редакторов творческих инструментов с React, Immer и Recoil в качестве основы нашего подхода. Чтобы лучше проиллюстрировать наш подход, мы включим несколько примеров кода и небольшой прототип.

вступление

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

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

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

Состояние приложения и состояние дизайна

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

Проектное состояние

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

Состояние дизайна изменяется каждый раз, когда концептуально изменяется дизайн, но не тогда, когда пользователь выполняет действия, не влияющие на дизайн.

Некоторые примеры действий, влияющих на состояние проекта:

  • Переместить элемент
  • Изменить свойства текста (цвет, размер, шрифт)
  • Групповые элементы

Определяет текущее состояние приложения, за исключением дизайна. Состояние приложения изменяется, когда пользователь взаимодействует с приложением и меню.

Некоторые примеры:

  • Выберите элемент
  • Открытие диалогового окна для редактирования свойств элемента
  • Показать контекстное меню
  • Подтвердить действие

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

Например:

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

Состояние приложения

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

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

Проектное состояние

При определении проектного состояния мы должны учитывать следующие соображения:

  • Иметь возможность получить и установить все состояние, чтобы мы могли легко реализовать:
    – Загрузка и сохранение дизайна
    – Отменить/повторить
  • Избегайте рендеринга всех элементов, когда изменяются только некоторые из них.

Один из самых простых способов реализовать отмену/возврат в приложении React — использовать Immer для создания патчей модификации.

Immer — это библиотека JS, которая позволяет нам записывать модификации данных в неизменном контексте (например, в состоянии React) «изменяемым способом».

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

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

Почему нам нужно получить и установить все состояние одновременно?

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

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

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

Использование одного состояния решило бы все эти проблемы.

  • Нет действий, которые изменяют несколько состояний
  • Все исправления применяются к состоянию, а не к нескольким распределенным состояниям.

Более простой способ удовлетворить этот выбор дизайна — сохранить все состояние дизайна в одном месте. Это заставило бы нас подумать, что useState<DesignState>(defaultState), как вы, вероятно, догадались, это приводит к тому, что мы теряем наше соображение «рендеринг большей части приложения»:

Не отображается большая часть приложения при изменении дизайна

Чтобы решить эту проблему, мы обычно используем Recoil, библиотеку управления состоянием для React.

Recoil имеет две основные концепции : атомы и селекторы.

Atoms : единица состояния, которую можно использовать аналогично useState, но глобально, например .

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

Селекторы : функции, создающие проекцию атома или другого селектора. Когда компонент React использует селектор, он будет перерисовываться только (с некоторыми оговорками) при изменении результата селектора.

Recoil также позволяет нам получать аргументы в функции, определяющей селектор, такие селекторы называются selectorFamily. Мы можем использовать это для создания selectorFamily, которое получает elementId и дает нам элемент.

При изменении элемента приведенный выше код запускает только обновление соответствующего элемента DesignElement и не отображает все элементы или компонент «Дизайн».

Внимательный наблюдатель может увидеть, что компонент «Дизайн» будет отображаться всякий раз, когда компонент добавляется или удаляется, вызывая повторную визуализацию всех элементов дизайна. Если это вызывает проблемы с производительностью для конкретного варианта использования, мы можем обернуть наш компонент DesignElement в файл React.memo.

Установите состояние

Поскольку мы хотим, чтобы наши наборы применялись к DesignState верхнего уровня (чтобы упростить отмену/повтор), мы можем создать обратные вызовы отдачи , чтобы инкапсулировать нашу логику модификации и создание патчей отмены/повторения.

Минимальный пример можно найти в этом Codesanbox.

Закрытие

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

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

В Zeppelin Labs мы помогаем основателям и растущим компаниям экспериментировать и создавать дифференцированные цифровые продукты, способствующие росту. Вы можете найти нас здесь . Или здесь . Или здесь .

Если вы хотите присоединиться к нашей команде, напишите нам по адресу [email protected]

Хотели бы стать партнером? напишите нам по адресу [email protected]

Подпишитесь на нашу рассылку здесь .