TypeScript Academy · Урок

Моделирование конечных автоматов

Представляйте конечные состояния с помощью дискриминируемых объединений

Урок 4 из 413 шагов

«Моделирование конечных автоматов» — бесплатный урок TypeScript Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения TypeScript Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс TypeScript Academy содержит 4 уроков всего.

Состояния как дискриминируемое объединение

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

type State =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: string }
  | { status: "error"; message: string };

const s: State = { status: "idle" };
console.log(s.status);

Данные для каждого состояния

Только состояние success содержит data, а только состояние error — message. Состояния простоя и загрузки не содержат дополнительных данных, что не позволяет устаревшим данным просачиваться между состояниями.

type State =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: number[] }
  | { status: "error"; message: string };

const ok: State = { status: "success", data: [1, 2, 3] };
console.log(ok.status, "data" in ok ? ok.data : null);

Отображение каждого состояния

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

type State =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: string }
  | { status: "error"; message: string };

function view(s: State): string {
  switch (s.status) {
    case "idle": return "Ready";
    case "loading": return "Loading...";
    case "success": return "Got: " + s.data;
    case "error": return "Failed: " + s.message;
  }
}
console.log(view({ status: "success", data: "users" }));

Определение переходов

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

type State =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: string }
  | { status: "error"; message: string };

function start(s: State): State {
  if (s.status === "idle") return { status: "loading" };
  return s;
}
console.log(start({ status: "idle" }).status);

Моделирование событий

Сами события также могут быть дискриминируемым объединением. Каждый тип события запускает определённый переход.

type Event =
  | { type: "FETCH" }
  | { type: "RESOLVE"; data: string }
  | { type: "REJECT"; message: string };

const e: Event = { type: "RESOLVE", data: "ok" };
console.log(e.type);

Полная функция переходов

Объедините объединения состояний и событий в одной функции обработки. Переключение по обоим признакам делает каждый переход явным и безопасным с точки зрения типов.

type State =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: string }
  | { status: "error"; message: string };
type Event =
  | { type: "FETCH" }
  | { type: "RESOLVE"; data: string }
  | { type: "REJECT"; message: string };

function reduce(s: State, e: Event): State {
  if (e.type === "FETCH") return { status: "loading" };
  if (e.type === "RESOLVE") return { status: "success", data: e.data };
  return { status: "error", message: e.message };
}
console.log(reduce({ status: "idle" }, { type: "FETCH" }).status);

Недопустимые состояния невозможно представить

Нельзя одновременно иметь data и error message. Структура объединения не допускает противоречивые состояния.

type State =
  | { status: "success"; data: string }
  | { status: "error"; message: string };

// const bad: State = { status: "success", data: "x", message: "y" };
// Allowed extra props would be flagged in strict object checks.
console.log("no contradictory states");

Защита от недопустимых переходов

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

type State = { status: "idle" } | { status: "loading" };
type Event = { type: "FETCH" } | { type: "CANCEL" };

function reduce(s: State, e: Event): State {
  if (s.status === "loading" && e.type === "CANCEL") return { status: "idle" };
  if (s.status === "idle" && e.type === "FETCH") return { status: "loading" };
  return s; // ignore invalid combos
}
console.log(reduce({ status: "loading" }, { type: "CANCEL" }).status);

Добавление проверки полноты

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

function assertNever(x: never): never { throw new Error("unhandled"); }
type State = { status: "idle" } | { status: "loading" } | { status: "done"; data: string };

function view(s: State): string {
  switch (s.status) {
    case "idle": return "idle";
    case "loading": return "loading";
    case "done": return s.data;
    default: return assertNever(s);
  }
}
console.log(view({ status: "done", data: "ok" }));

Начальное и конечные состояния

Определите ясное начальное состояние (часто idle) и конечные состояния (success или error). Так Вы документируете жизненный цикл машины.

type State =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: string }
  | { status: "error"; message: string };

const initial: State = { status: "idle" };
console.log("Start:", initial.status);

Полная небольшая машина состояний

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

type State = { status: "idle" } | { status: "loading" } | { status: "success"; data: string };
type Event = { type: "FETCH" } | { type: "RESOLVE"; data: string };

function reduce(s: State, e: Event): State {
  if (e.type === "FETCH") return { status: "loading" };
  return { status: "success", data: e.data };
}
let st: State = { status: "idle" };
st = reduce(st, { type: "FETCH" });
st = reduce(st, { type: "RESOLVE", data: "hi" });
console.log(st.status, st.status === "success" ? st.data : "");

Быстрая проверка: машины состояний

Проверьте, насколько хорошо Вы поняли моделирование машин состояний.

Итоги: моделирование машин состояний

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

type State = { status: "idle" } | { status: "loading" } | { status: "success"; data: string };
const s: State = { status: "success", data: "done" };
console.log(s.status === "success" ? s.data : "");
Можно начать бесплатно

Изучай TypeScript с ИИ-репетитором — бесплатно

Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.

Курсы
101
Уроки
352

Часто задаваемые вопросы

Урок «Моделирование конечных автоматов» бесплатный?

Да — полный текст урока «Моделирование конечных автоматов» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс TypeScript Academy, подпишись на CoddyKit PRO. Курс TypeScript Academy содержит 4 уроков всего.

Чему я научусь в уроке «Моделирование конечных автоматов»?

Представляйте конечные состояния с помощью дискриминируемых объединений Ты практикуешь TypeScript Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать TypeScript Academy?

Предыдущий опыт не требуется. TypeScript Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.

Сколько времени занимает урок «Моделирование конечных автоматов»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке TypeScript Academy?

Да. Каждый урок TypeScript Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Создание дискриминируемых объединений
  2. Сужение по дискриминатору
  3. Проверка полноты с помощью never
  4. Моделирование конечных автоматов
← Назад к TypeScript Academy