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

Работа с TypeScript

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

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

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

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

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

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

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

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

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

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

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

src/store/create-machine.ts
import type { TypedCreateConfigFn, TypedCreateEffectFn, TypedCreateMachineFn, TypedCreateReducerFn } from "lite-fsm"; import { createConfig as _createConfig, createEffect as _createEffect, createMachine as _createMachine, createReducer as _createReducer, } from "lite-fsm"; 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 { TypedUseMachineHook, TypedUseSelectorHook, TypedUseTransitionHook } from "lite-fsm/react"; import { useManager as _useManager, useSelector as _useSelector, useTransition as _useTransition, } from "lite-fsm/react"; import type { AppEvents } from "./types"; import type { AppState, FSMConfigType } from "."; // Создаем типизированные версии хуков export const useTransition: TypedUseTransitionHook<AppEvents> = _useTransition; export const useSelector: TypedUseSelectorHook<AppState> = _useSelector; export const useManager: TypedUseMachineHook<FSMConfigType, AppEvents> = _useManager;

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

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

src/store/index.ts
import { counter } from "./machines/counter"; import { MachineManager, type MachinesState } from "lite-fsm"; import { devToolsMiddleware, immerMiddleware } from "lite-fsm/middleware"; import type { AppEvents, Dependencies } from "./types"; // Конфигурация всех автоматов const cfg = { counter }; // Тип конфигурации, нужен для типизации менеджера export type FSMConfigType = typeof cfg; // Функция создания store export const makeStore = () => { const middleware = [ immerMiddleware, devToolsMiddleware({ blacklistActions: [], }), ]; const manager = MachineManager<FSMConfigType, AppEvents>(cfg, { middleware, onError: console.error, }); manager.setDependencies<Dependencies>({ getState: manager.getState, services: { logger: console, }, }); return manager; }; // Экспортируем типы для использования в приложении export type AppState = MachinesState<FSMConfigType>; export type AppStore = ReturnType<typeof makeStore>;

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

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

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

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

src/store/machines/counter.ts
import { createMachine, createConfig } 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" // Определяем полный тип событий с payload export type Events = | { type: "INCREMENT"; payload: { step: number } } | { type: "DECREMENT"; payload: { step: number } } | { type: "RESET" }; // Используем выведенные типы export const counter = createMachine({ config, initialState: "IDLE", // TypeScript проверяет, что это одно из состояний initialContext: { count: 0, }, // ... });

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

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

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

import type { FSMEvent } from "lite-fsm"; import { createMachine, createConfig } from "../create-machine"; // Создаем типизированную конфигурацию const config = createConfig({ IDLE: { INCREMENT: "COUNTING", DECREMENT: "IDLE", RESET: "IDLE", SET_VALUE: "IDLE", }, COUNTING: { INCREMENT: null, DECREMENT: "IDLE", RESET: "IDLE", }, }); // Используем FSMEvent для типизации событий с различными payload export type Events = | FSMEvent<"INCREMENT"> // Без payload | FSMEvent<"DECREMENT"> // Без payload | FSMEvent<"RESET"> // Без payload | FSMEvent<"SET_VALUE", { value: number }>; // С payload export const counter = createMachine({ config, initialState: "IDLE", initialContext: { count: 0, }, reducer: (state, action) => { const { state: currentState, context } = state; switch (action.type) { case "INCREMENT": return { state: currentState, context: { ...context, count: context.count + 1, }, }; case "DECREMENT": return { state: currentState, context: { ...context, count: Math.max(0, context.count - 1), }, }; case "RESET": return { state: currentState, context: { ...context, count: 0, }, }; case "SET_VALUE": // TypeScript знает, что у action.payload есть свойство value return { state: currentState, context: { ...context, count: action.payload.value, }, }; default: return state; } }, }); // Примеры использования: // 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 для каждого типа события.

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

Теперь в компонентах 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 предупредит вас о несоответствии типов в вашем коде.
  2. Автодополнение в IDE - получите подсказки о доступных состояниях, событиях и контексте.
  3. Проще рефакторинг - при изменении типов TypeScript укажет на места, которые нужно обновить.
  4. Документирование кода - типы служат документацией, которая всегда актуальна.
  5. Предотвращение ошибок типов - избегайте ошибок из-за неправильных типов данных.

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

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

Заключение

Использование TypeScript с lite-fsm позволяет создавать надежные и понятные приложения, где многие ошибки выявляются на этапе компиляции. Предварительно типизированные функции API избавляют от необходимости повторно указывать типы и обеспечивают согласованность типов во всем приложении.

Last updated on