Skip to Content
👋 Добро пожаловать в документацию lite-fsm!
ПрактикаРабота с TypeScript

Работа с TypeScript

TypeScript проверяет события, payload-ы, состояния и зависимости effects на этапе компиляции. В этом разделе показано, как настроить типизированные фабрики и React-хуки для приложения на lite-fsm.

Структура проекта

Ниже представлена структура файлов, которую мы будем использовать в примерах:

src/ ├── App.tsx # Корневой компонент React-приложения └── store/ # Директория для управления состоянием ├── index.ts # Экспорт store и типов ├── types.ts # Общие типы приложения ├── create-machine.ts # Типизированные версии API функций ├── hooks.ts # Типизированные React-хуки └── machines/ # Директория с автоматами ├── counter.ts # Автомат для счетчика

Предварительная типизация функций API

Обычный паттерн для TypeScript-проектов — создать локальные типизированные версии функций API (createMachine, createEffect и т.д.). Они фиксируют union событий и тип зависимостей приложения, чтобы не повторять generics в каждой машине.

Шаг 1: Определение базовых типов событий и зависимостей

Сначала определите базовые типы для вашего приложения:

src/store/types.ts
import type { AppState } from "."; import type * as counter from "./machines/counter"; // Объединяем все типы событий из всех автоматов export type AppEvents = counter.Events; // Определяем тип зависимостей, которые будут доступны в эффектах export type Dependencies = { logger: { log: (message: string) => void; }; getState: () => AppState; };

Шаг 2: Создание типизированных версий функций API

Создайте файл, который будет реэкспортировать функции lite-fsm с предварительно примененными типами:

src/store/create-machine.ts
import type { TypedCreateConfigFn, TypedCreateEffectFn, TypedCreateMachineFn, TypedCreateReducerFn, } from "@lite-fsm/core"; import { createConfig as _createConfig, createEffect as _createEffect, createMachine as _createMachine, createReducer as _createReducer, } from "@lite-fsm/core"; import type { AppEvents, Dependencies } from "./types"; // Типизированные версии функций для приложения export const createMachine: TypedCreateMachineFn<AppEvents, Dependencies> = _createMachine; export const createReducer: TypedCreateReducerFn<AppEvents> = _createReducer; export const createConfig: TypedCreateConfigFn<AppEvents> = _createConfig; export const createEffect: TypedCreateEffectFn<AppEvents, Dependencies> = _createEffect;

Шаг 3: Типизированные хуки для работы с React

Аналогичным образом, создайте типизированные версии React-хуков:

src/store/hooks.ts
import type { TypedUseManagerHook, TypedUseSelectorHook, TypedUseTransitionHook } from "@lite-fsm/react"; import { useManager as _useManager, useSelector as _useSelector, useTransition as _useTransition, } from "@lite-fsm/react"; import type { FSMConfigType } from "."; import type { AppEvents } from "./types"; // Типизированные версии хуков. // TypedUseSelectorHook принимает store config (FSMConfigType), не computed AppState — // `MachinesState<S>` будет вычислен автоматически. export const useTransition: TypedUseTransitionHook<AppEvents> = _useTransition; export const useSelector: TypedUseSelectorHook<FSMConfigType> = _useSelector; export const useManager: TypedUseManagerHook<FSMConfigType, AppEvents> = _useManager;

Шаг 4: Создание store

Теперь нужно объединить все автоматы в единый store и экспортировать типы:

src/store/index.ts
import { MachineManager, type MachinesState } from "@lite-fsm/core"; import { devToolsMiddleware } from "@lite-fsm/middleware/devTools"; import { immerMiddleware } from "@lite-fsm/middleware/immer"; import { counter } from "./machines/counter"; import type { AppEvents, Dependencies } from "./types"; const cfg = { counter }; export type FSMConfigType = typeof cfg; export type AppState = MachinesState<FSMConfigType>; export const makeStore = () => { const manager = MachineManager<FSMConfigType, AppEvents>(cfg, { middleware: [immerMiddleware, devToolsMiddleware({ blacklistActions: [] })], onError: console.error, }); manager.setDependencies({ getState: manager.getState, logger: console, }); return manager; }; export type AppStore = ReturnType<typeof makeStore>;

Шаг 5: Создадим базовый шаблон автомата counter

src/store/machines/counter.ts
import type { FSMEvent } from "@lite-fsm/core"; import { createMachine } from "../create-machine"; export type Events = FSMEvent<"DO_INIT">; export const counter = createMachine({ config: { IDLE: { DO_INIT: "READY", }, READY: {}, }, initialState: "IDLE", initialContext: {}, });

Автоматический вывод типов событий и состояний

При использовании типизированных функций TypeScript выводит доступные состояния из конфигурации автомата. Это уменьшает дублирование между config и типами.

src/store/machines/counter.ts
import { createConfig, createMachine } from "../create-machine"; const config = createConfig({ IDLE: { INCREMENT: "COUNTING", RESET: "IDLE", }, COUNTING: { INCREMENT: null, DECREMENT: null, RESET: "IDLE", }, }); // Автоматически выводим тип состояний из конфигурации export type State = keyof typeof config; // => "IDLE" | "COUNTING" export type Events = | { type: "INCREMENT"; payload: { step: number } } | { type: "DECREMENT"; payload: { step: number } } | { type: "RESET" }; export const counter = createMachine({ config, initialState: "IDLE", initialContext: { count: 0, }, // ... });

Такой подход уменьшает дублирование: при изменении config выведенный union состояний обновится вместе с ним.

Использование FSMEvent для типизации событий

Библиотека lite-fsm предоставляет встроенный тип FSMEvent для описания событий с payload:

Reducer в следующем примере написан в стиле Immer и рассчитан на immerMiddleware, подключенный в makeStore выше. Если middleware не используется, reducer должен вернуть новый объект { state, context }.

import type { FSMEvent } from "@lite-fsm/core"; import { createConfig, createMachine } from "../create-machine"; const config = createConfig({ IDLE: { INCREMENT: "COUNTING", DECREMENT: "IDLE", RESET: "IDLE", SET_VALUE: "IDLE", }, COUNTING: { INCREMENT: null, DECREMENT: "IDLE", RESET: "IDLE", }, }); // FSMEvent — короткая запись для action-типов с payload и без export type Events = | FSMEvent<"INCREMENT"> | FSMEvent<"DECREMENT"> | FSMEvent<"RESET"> | FSMEvent<"SET_VALUE", { value: number }>; export const counter = createMachine({ config, initialState: "IDLE", initialContext: { count: 0, }, reducer: (state, action) => { switch (action.type) { case "INCREMENT": state.context.count += 1; return; case "DECREMENT": state.context.count = Math.max(0, state.context.count - 1); return; case "RESET": state.context.count = 0; return; case "SET_VALUE": // TypeScript знает, что action.payload содержит value: number state.context.count = action.payload.value; return; } }, }); // Примеры использования: // OK: manager.transition({ type: "INCREMENT" }); // OK: manager.transition({ type: "SET_VALUE", payload: { value: 42 } }); // Ошибка TypeScript: manager.transition({ type: "SET_VALUE" }); // Отсутствует payload // Ошибка TypeScript: manager.transition({ type: "INCREMENT", payload: { value: 5 } }); // Лишний payload

FSMEvent делает форму событий явной: событие без payload не принимает payload, а событие с payload требует поле payload нужной формы.

Использование типизированных хуков в компонентах

В компонентах React используйте типизированные хуки:

src/App.tsx
import "./styles.css"; import { useSelector, useTransition } from "./store/hooks"; export default function App() { const transition = useTransition(); const counterState = useSelector((s) => s.counter.state); const count = useSelector((s) => s.counter.context.count); return ( <div className="App"> <h1>Counter state: {counterState}</h1> <h1>Counter value: {count}</h1> <button onClick={() => { transition({ type: "INCREMENT", payload: { step: 1 } }); }} > +1 </button> <button onClick={() => { transition({ type: "DECREMENT", payload: { step: 1 } }); }} disabled={!count} > -1 </button> <button onClick={() => { transition({ type: "RESET" }); }} > Сбросить </button> </div> ); }

Преимущества типизации

Предварительная типизация функций API lite-fsm даёт следующие преимущества:

  1. Проверка типов во время компиляции — TypeScript подсветит несоответствие событий, payload-ов и состояний.
  2. Автодополнение в IDE — доступны подсказки по состояниям, событиям и контексту.
  3. Рефакторинг — при изменении типов TypeScript укажет места, которые нужно обновить.
  4. Документирование контракта — типы показывают форму событий, контекста и dependencies.
  5. Меньше runtime-ошибок — часть ошибок переходов и payload-ов ловится до запуска приложения.

Дополнительные рекомендации

  • Разделяйте типы по машинам — определяйте Context, State и Events рядом с конкретным автоматом.
  • Используйте union-типы для событий — так TypeScript сможет сужать action по action.type.
  • Используйте вывод типов — выводите union состояний из config, когда это возможно.
  • Включайте строгий режим TypeScript — используйте "strict": true в tsconfig.json.

Заключение

TypeScript с lite-fsm делает контракты машин явными. Локальные типизированные фабрики и хуки уменьшают повторение generics и дают единый способ проверять события, состояния, context shape и dependencies во всём приложении.

Last updated on