Давайте рассмотрим проект MauiReactor и паттерн «Модель-Представление-Обновление» (MVU) на примере простого приложения .NET MAUI.
Создавая приложения на .NET MAUI, мы привыкли работать со страницами контента, написанными на XAML, моделями представлений со свойствами привязки и моделями данных. Однако сегодня существует альтернатива, которая позволяет забыть о привязках благодаря более реактивному подходу, похожему на те, что применяются на других платформах. В этой статье мы рассмотрим проект MauiReactor и паттерн MVU (Model-View-Update), а также то, как вы можете использовать его в собственных проектах .NET MAUI. Давайте начнем!
Что такое паттерн MVU?
MVU расшифровывается как Model-View-Update (Модель-Представление-Обновление). Этот паттерн приобрел популярность в таких фреймворках, как React, Flutter и других. Каждый элемент определяется следующим образом:
- Модель (Model): это состояние вашего экрана в определенный момент времени. Важно не путать ее с моделями классов; скорее, это все, что необходимо для отрисовки пользовательского интерфейса, например счетчик, список задач и т. д.
- Представление (View): это функция, которая принимает текущее состояние и возвращает то, как должен выглядеть интерфейс. Важно знать, что при изменении состояния представление генерируется заново.
- Обновление (Update): этот элемент является фундаментальным в паттерне, так как это единственное место, где состояние может быть изменено. Когда пользователь выполняет действие в графическом интерфейсе, состояние обновляется, что приводит к повторной генерации представления.
В результате получается однонаправленный и предсказуемый поток данных. В отличие от MVVM, в MVU нет двунаправленных привязок или уведомлений через события. Вместо этого его можно охарактеризовать как состояние, функцию, которая отрисовывает это состояние, и контролируемый способ обновления того, когда оно будет отрисовано.
Что такое MauiReactor?
MauiReactor — это библиотека на GitHub, созданная Адольфо Маринуччи (Adolfo Marinucci), которая реализует компонентный фреймворк пользовательского интерфейса поверх .NET MAUI. Это означает, что вы можете создавать пользовательские интерфейсы на основе паттерна MVU, используя только декларативный и текучий (fluent) код на C#.
Преимущество MauiReactor заключается в том, что Маринуччи создал эквивалент для каждого элемента управления .NET MAUI. Например, этот код XAML:
В MauiReactor будет переведен так:
Пока не беспокойтесь о реализации; я хочу, чтобы вы обратили внимание на то, как интерфейс определяется с помощью текучих методов, что делает код читаемым. Кроме того, здесь нет кода XAML или привязок.
Настройка проекта .NET MAUI для MauiReactor
MauiReactor распространяется через пакет NuGet Reactor.Maui, хотя также можно установить предварительно настроенный шаблон, как указано в официальном репозитории. Для этого выполните следующую команду в терминале:
После создания шаблона мы можем создать новый проект с помощью следующей команды:
Одним из важных шагов для работы MauiReactor в проекте .NET MAUI является замена традиционного вызова builder.UseMauiApp<App>() на builder.UseMauiReactorApp<MainPage>(). Это указывает MauiReactor, что компонент MainPage будет первым отображаемым экраном.
Создание компонента без состояния в MauiReactor
Теперь, когда проект настроен, давайте посмотрим, как создать компонент в MauiReactor. Давайте создадим простой компонент, который выводит пользователю сообщение и не имеет состояния. Для этого я создам новый класс с именем MainPage.cs со следующим содержимым:
Давайте присмотримся к коду внимательнее:
- Класс MainPage наследуется от Component, который является базовым классом для каждого компонента без состояния в MauiReactor.
- Метод Render переопределен и возвращает визуальное дерево экрана.
- Создается страница с заголовком Habit Tracker и элементом Label в качестве содержимого.
- Мы используем цепочку методов (method chaining) для настройки свойств метки.
При запуске приложения мы получим примерно следующий результат:
Теперь сделаем шаг вперед и реализуем состояние для реагирования на действия пользователя.
Создание компонента с состоянием в MauiReactor
Чтобы добавить состояние в компонент MauiReactor, нужно сделать две вещи. Во-первых, определить класс, представляющий состояние, и во-вторых, унаследовать компонент от Component<TState>. Вернемся к нашему файлу MainPage.cs и изменим его следующим образом:
Давайте снова подробно проанализируем класс:
- Мы определили класс CounterState с единственным свойством Counter. Этот класс будет представлять состояние экрана и является частью Model в паттерне.
- Мы изменили наследование MainPage, добавив параметр типа CounterState. Это позволяет нам обращаться к свойству State, которое дает доступ к состоянию компонента.
- Свойство State используется там, где мы хотим отобразить информацию о компоненте. В нашем примере мы используем интерполированную строку для отображения состояния Counter в элементе Label. Здесь мы видим часть View паттерна MVU.
- Кнопка получает начальный текст для отображения пользователю в интерфейсе, а также обратный вызов (callback), который будет выполняться при нажатии.
- Внутри обратного вызова вызывается метод SetState(s => s.Counter++), что представляет часть Update паттерна.
Мы увидели различные части паттерна MVU в действии на предыдущем примере кода. Что же на самом деле происходит за кулисами?
Внутренне при вызове SetState компонент помечается как недействительный. Затем метод Render выполняется повторно, создавая новое визуальное дерево с обновленным состоянием. Наконец, новое дерево сравнивается с предыдущим, и к нативным элементам управления применяются только различия. Результат выполнения выглядит следующим образом:
Теперь, когда мы освоили основы работы MauiReactor, давайте создадим более полезное приложение.
Создание приложения в стиле трекера привычек
Мы собираемся создать простое приложение, которое позволяет отображать список привычек с названием, ежедневной целью и прогрессом. Для этого начнем с создания новой модели Habits:
Интересная особенность предыдущей модели заключается в том, что свойство IsCompleted вычисляется автоматически в зависимости от того, была ли достигнута цель, и возвращает true, если это так.
Теперь давайте обновим MainPage следующим образом:
В предыдущем коде происходит несколько интересных вещей:
- Мы определили в MainPageState свойство типа списка привычек, инициализированное как пустое.
- OnMounted — это метод жизненного цикла компонента, который MauiReactor вызывает только один раз при первом отображении. Поэтому мы используем его для инициализации списка данными.
- В визуальной части мы используем ScrollView, чтобы список привычек мог прокручиваться при увеличении.
- Мы используем элементы управления .NET MAUI для определения графического интерфейса.
Предыдущий код определяет только Label с основным заголовком страницы, но он все еще не выводит информацию списка. Чтобы исправить это, мы создадим новый меньший повторно используемый компонент.
Создание повторно используемого компонента в MauiReactor
Хотя мы могли бы создать весь интерфейс из основного компонента, в идеале всегда иметь небольшие блоки, которые можно переиспользовать во всем приложении. В данном случае мы создадим компонент, похожий на карточку, который принимает привычку и выводит карточку с ее названием, прогрессом и кнопкой для его увеличения.
Мы реализуем это, создав файл HabitCard.cs:
Предыдущий код демонстрирует некоторые концепции, которые мы еще не объяснили, поэтому я воспользуюсь возможностью указать на них:
- Класс объявляется как partial, поскольку MauiReactor использует генератор кода для создания плавных (fluent) методов из наших свойств.
- Вы можете заметить, что мы использовали атрибуты [Prop], которые позволят родительскому компоненту настраивать свойства.
- Используется свойство типа Action, которое позволит дочернему компоненту уведомить родительский о нажатии кнопки, причем родительскому не обязательно знать, что должно произойти дальше.
- В некоторых местах приложения значения пользовательского интерфейса будут обновляться в соответствии с состоянием привычки.
Теперь, когда этот новый компонент готов, давайте вернемся на главную страницу, чтобы интегрировать его.
Использование дочернего компонента в MauiReactor
Пришло время интегрировать новый дочерний компонент в главный компонент. Для этого мы доработаем метод Render, создав цикл для обхода каждой привычки и ее отображения с помощью нового компонента:
В предыдущем коде мы делаем следующее:
- Мы вычисляем, сколько привычек выполнено, чтобы показать это пользователю.
- Создается список children из узлов VisualNode, которые являются визуальными узлами MauiReactor. Каждый элемент будет иметь название и прогресс выполнения задач.
- Мы проходим по каждой привычке и для каждой из них добавляем подкомпонент HabitCard. Поскольку мы используем атрибут [Prop] в свойствах подкомпонента, мы можем настроить то, что произойдет при нажатии его кнопки.
- В OnIncrement мы вызываем метод SetState, который инициирует новый рендеринг. И подзаголовок, и карточка обновятся автоматически.
- Мы добавляем кнопку с текстом Reset day, которая проходит по всем привычкам и сбрасывает их счетчик в ноль.
- Мы возвращаем новую страницу содержимого с вертикальным списком, содержащим все привычки из списка.
То, что мы увидели в реализации — это то, что элементы управления никоим образом не манипулируются напрямую. Скорее, по мере изменения состояния весь интерфейс регенерируется заново, а MauiReactor применяет только различия.
При запуске приложения мы получим следующий результат:
Мы видим, что в результате получается полнофункциональное приложение с архитектурой, совершенно отличной от той, к которой мы привыкли.
Заключение
В этой статье мы рассмотрели, что такое MauiReactor и паттерн MVU применительно к проектам .NET MAUI. Мы увидели, что этот паттерн предлагает архитектуру, отличную от традиционного MVVM, в которой нет привязок (bindings) и уведомлений.
Это, безусловно, интересное предложение, хотя мы должны помнить, что оно не имеет официальной поддержки от команды .NET MAUI. Я приглашаю вас изучить репозиторий и поэкспериментировать с собственными проектами.










