Skip to content

Справочник

Типы TypeScript

Все публичные типы экспортируются из корня пакета:

ts
import type {
  // Основной конфиг
  MachineConfig,
  StateConfig,
  TransitionConfig,
  SubMachineConfig,

  // Функции
  Guard,
  Action,

  // События
  EventObject,

  // Runtime
  MachineInstance,
  UseMachineOptions,
  TransitionRecord,
  MachineSnapshot,
  TransitionResult,

  // Wizard
  WizardStep,
  WizardOptions,
  WizardInstance,

  // Store
  MachineStoreAPI,

  // Утилиты
  Ctx,
} from 'vue-state-machine'

Вывод дженериков

TypeScript выводит TState, TEvent и TContext из конфига, переданного в defineMachine. Аннотировать их вручную требуется редко:

ts
const machine = defineMachine({
  id: 'traffic',
  initial: 'red', // TS выводит TState = 'red' | 'green' | 'yellow'
  states: {
    red: { on: { NEXT: { target: 'green' } } }, // TEvent = 'NEXT'
    green: { on: { NEXT: { target: 'yellow' } } },
    yellow: { on: { NEXT: { target: 'red' } } },
  },
})

const { state } = useMachine(machine)
// state: Ref<'red' | 'green' | 'yellow'>
// send принимает только 'NEXT' — остальные строки — ошибка компиляции

Для сложных случаев можно аннотировать явно:

ts
const machine = defineMachine<
  'idle' | 'loading' | 'error' | 'success',
  'SUBMIT' | 'SUCCESS' | 'FAILURE' | 'RETRY',
  { attempts: number; error: string | null }
>({ ... })

Совместимость с SSR

СценарийПоведение
Серверный рендерОсновные модули (defineMachine, MachineRunner, useMachine) не обращаются к window / document / localStorage
persist на сервереМолча отключена — проверка typeof window === 'undefined' внутри composable
ГидратацияВызовите restore(serverSnapshot) внутри onMounted, чтобы гидрировать из серверного снапшота без повторного запуска guards/actions
snapshotСериализуется через JSON.stringify — передавайте с сервера на клиент через Nuxt useState, useServerState или инъекцию <script>

Пример SSR в Nuxt:

vue
<script setup lang="ts">
import { useMachine } from 'vue-state-machine'
import { onMounted } from 'vue'

// Снапшот, переданный с сервера через useAsyncData / useState
const serverSnapshot = useState('checkout-snapshot')

const { state, send, restore } = useMachine(checkoutMachine)

onMounted(() => {
  if (serverSnapshot.value) restore(serverSnapshot.value)
})
</script>

Архитектура

defineMachine(config)

    ▼ dev-time validation + type narrowing
MachineConfig<TState, TEvent, TContext>

    ▼ created inside useMachine()
MachineRunner  (pure class, zero Vue deps)
    │  getCurrentState() / getContext()
    │  canTransition(event) → boolean
    │  enqueue(event)  ──────────────────────────────┐
    │  transition(event) → Promise<TransitionResult>  │
    │                                                 │
    │  EventQueue (sequential processing)             │
    │  ├── guard check  (sync, exception = false)     │
    │  ├── exit actions (await each)                  │
    │  ├── transition actions (await each)            │
    │  ├── state update                               │
    │  └── entry actions (await each)                 │
    │       └── Partial<TContext> merged into context ◄┘

    │  Parallel regions
    │  ├── SubMachineRunner per region (activated on state entry)
    │  ├── send() dispatches to all regions
    │  └── "last declared wins" on context conflict

    ▼ wrapped in Vue reactivity
useMachine(config, options)
    │  state:   shallowRef<TState>
    │  context: shallowRef<TContext>
    │  history: shallowRef<TransitionRecord[]>  (FIFO, historyLimit)
    │  send()   → enqueue → sync refs after result
    │  matches() / can()
    │  snapshot / restore()
    │  onMounted: load persist snapshot
    │  on transition: save persist snapshot

    ├──▶ MachineStore (provide/inject via VueMachinePlugin)
    │        register() on composable creation
    │        useSharedMachine() → singleton by config.id


Vue components (template, setup)

useWizard(steps, options)
    │  buildWizardMachine() → generates MachineConfig from steps array
    │  useMachine(generatedConfig)
    │  next() → await canProceed → send('NEXT')
    │  goTo(id) → await canProceed (if forward) → send('GOTO_<id>')
    │  prev() → send('PREV')


WizardInstance (currentStep, progress, isFirst, isLast, history, ...)

VueMachineDevtools (separate entry point /devtools)
    │  reads MachineStore via app._context.provides
    │  hooks into __VUE_DEVTOOLS_GLOBAL_HOOK__
    │  emits timeline events per transition

Vue DevTools browser extension panel "State Machines"

Размер бандла и peer-зависимости

Точка входаPeer-зависимостиGzip
vue-state-machinevue ^3.3≤ 4 КБ (ядро)
vue-state-machine/devtoolsvue ^3.3, @vue/devtools-api (peer)отдельный чанк
  • Поставляется как tree-shakeable ESM (dist/index.mjs) и CommonJS (dist/index.cjs)
  • "sideEffects": false в package.json — бандлеры могут удалять неиспользуемые экспорты
  • Точка входа /devtools — отдельный чанк; импорт внутри блока if (import.meta.env.DEV) гарантирует исключение из продакшен-бандлов стандартным tree-shaking'ом

Лицензия

MIT