Что такое Action в паттерне MVI (Model-View-Intent)?

Ответ

В архитектуре MVI (Model-View-Intent) Action (или Intent) — это иммутабельный объект-сообщение, который описывает намерение пользователя или системы изменить состояние приложения. Это единственный способ для View инициировать изменения.

Роль в однонаправленном потоке данных:

  1. View наблюдает за пользовательскими событиями (клики, ввод) и преобразует их в объекты Action.
  2. Intent (или обработчик) принимает поток Actions, применяет к ним необходимую бизнес-логику (например, асинхронные запросы) и выдает новые намерения для изменения Model.
  3. Model (состояние) обновляется на основе этих намерений, и новое состояние передается обратно во View для рендеринга.

Пример реализации на C# (упрощенно):

// 1. Определяем Actions как типы (часто используют record или sealed class)
public abstract record CounterAction;
public record IncrementAction(int Step = 1) : CounterAction;
public record DecrementAction(int Step = 1) : CounterAction;
public record LoadDataAction : CounterAction;

// 2. Модель (состояние)
public record CounterState(int CurrentCount, bool IsLoading);

// 3. Обработчик (Intent/Reducer), который создает новое состояние на основе Action
public static CounterState Reduce(CounterState currentState, CounterAction action)
{
    return action switch
    {
        IncrementAction inc => currentState with { CurrentCount = currentState.CurrentCount + inc.Step },
        DecrementAction dec => currentState with { CurrentCount = currentState.CurrentCount - dec.Step },
        LoadDataAction => currentState with { IsLoading = true },
        _ => currentState // Игнорируем неизвестные действия или возвращаем текущее состояние
    };
}

Ключевые принципы для Action в MVI:

  • Иммутабельность: Каждый Action — это неизменяемый "пакет" данных.
  • Детерминированность: Для одного и того же Action и текущего состояния результат должен быть предсказуем.
  • Серийность: Actions обрабатываются последовательно, что упрощает отладку и воспроизведение состояния.
  • Отсутствие побочных эффектов: Сами Actions не содержат логики. Побочные эффекты (запросы к API) обрабатываются отдельно (например, в Middleware или сайд-эффект менеджере).

Ответ 18+ 🔞

Слушай, вот объясняю про MVI, как будто это не архитектура, а пиздёж в баре после пятой рюмки. Action — это, блядь, такая бумажка с заявлением: «Хочу, чтобы в приложении вот это поменялось!». Представь, ты в ЖЭКе стоишь — тебе надо окно поменять. Ты не лезешь сам с молотком, а пишешь заёб: «Уважаемые, в квартире 322 окно разъёбано, сделайте что-нибудь». Вот этот заёб и есть Action.

В MVI всё по понятиям работает:

  1. View — это ты, мудак, который тыкает в экран. Нажал кнопку — сразу пишешь заявление (Action): «Хочу, чтобы счётчик увеличился».
  2. Intent (или редьюсер) — это такой бюрократ-обработчик, который получает твоё заявление и говорит: «Ага, понял, ща сделаем». Он может сам позвонить куда надо (сделать запрос к API), но само заявление не меняет — он только на его основе решает, как поменять состояние (Model).
  3. Model — это, сука, реальное состояние дел в квартире. Окно было разбито, стало целое. После того как бюрократ обработал твой Action, состояние меняется, и View говорит: «О, окно починили, теперь можно не мёрзнуть».

Вот тебе пример на C#, чтоб вообще всё стало ясно, как божий день:

// 1. Action — это типы заявлений. Делаем их на record'ах, они иммутабельные, как мёртвый ёж.
public abstract record CounterAction;
public record IncrementAction(int Step = 1) : CounterAction;
public record DecrementAction(int Step = 1) : CounterAction;
public record LoadDataAction : CounterAction;

// 2. Model — состояние, которое все наблюдают.
public record CounterState(int CurrentCount, bool IsLoading);

// 3. Редьюсер — тот самый бюрократ, который решает, что делать с заявлением.
public static CounterState Reduce(CounterState currentState, CounterAction action)
{
    return action switch
    {
        IncrementAction inc => currentState with { CurrentCount = currentState.CurrentCount + inc.Step },
        DecrementAction dec => currentState with { CurrentCount = currentState.CurrentCount - dec.Step },
        LoadDataAction => currentState with { IsLoading = true },
        _ => currentState // Если заявление непонятное — игнорируем, хуй с ним.
    };
}

А теперь, блядь, главные правила, чтобы не обосраться с Actions:

  • Иммутабельность — твоё заявление нельзя подтереть или переписать. Отправил и всё. Хуй поменяешь.
  • Детерминированность — один и тот же Action при одном и том же состоянии даёт одинаковый результат. Не должно быть: «Ой, сегодня пятница, поэтому +1, а в субботу — хуй знает что».
  • Серийность — заявления обрабатываются по очереди, как в нормальной очереди в туалет. Нельзя вписаться без очереди — так проще отлаживать.
  • Без побочки — в самом Action не должно быть логики, типа «пойти запросить данные с сервера». Это отдельная история, для этого есть сайд-эффекты, которые обрабатываются где-то рядом, но не в самом заявлении.

Короче, Action — это просто бумажка, констатация факта: «Я, пользователь, нажал сюда. Ваше дело решать, что делать дальше». Вся магия происходит в обработчике. Если держать эту дисциплину, то код живёт долго и счастливо, а если нет — получается пиздец, который потом разгребать, как говно за слоном.