Справочник
Архитектура
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 ◄┘
│ (contextPatch on TransitionResult = union of all
│ partials returned this transition, distinct from
│ nextContext = the full resulting context)
│
│ Parallel regions
│ ├── SubMachineRunner per region (activated on state entry)
│ ├── context deep-cloned (structuredClone) per instance/region
│ ├── send() dispatches to all regions
│ └── "last declared wins" on context conflict — only the fields
│ a region's own actions touched are ever merged/compared
│
▼ wrapped in Vue reactivity
useMachine(config, options)
│ state: shallowRef<TState>
│ context: shallowRef<TContext>
│ history: shallowRef<TransitionRecord[]> (FIFO, historyLimit)
│ send() → enqueue → sync refs after result (including a
│ region-only context change, via TransitionResult.changed)
│ matches() / can()
│ snapshot / restore()
│ onMounted: load persist snapshot
│ on transition: save persist snapshot
│
├──▶ MachineStore (provide/inject via VueMachinePlugin)
│ register()/retain() on composable creation, reference-counted
│ onUnmounted: unregister() (removed once refcount hits 0)
│ useSharedMachine() → singleton by config.id, retain()s on reuse
│
▼
Vue components (template, setup)
useWizard(steps, options)
│ buildWizardMachine() → generates MachineConfig from steps array,
│ with a unique id per instance (options.id, or auto-generated)
│ useMachine(generatedConfig)
│ next() → await canProceed(context.value) → send('NEXT')
│ goTo(id) → await canProceed (if forward) → send('GOTO_<id>')
│ prev() → send('PREV')
│ onEnter/onLeave may return Partial<TContext> → merged into the
│ same live context canProceed reads
│
▼
WizardInstance (currentStep, progress, isFirst, isLast, history, context, ...)
VueMachineDevtools (separate entry point /devtools)
│ reads MachineStore via app._context.provides
│ hooks into __VUE_DEVTOOLS_GLOBAL_HOOK__
│ emits one timeline event per machine on each visitComponentTree poll
│ (a live snapshot per inspection, not a push per send())
▼
Vue DevTools browser extension panel "State Machines"Совместимость с 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 '@macrulez/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>Размер бандла и peer-зависимости
| Точка входа | Peer-зависимости | Gzip |
|---|---|---|
@macrulez/vue-state-machine | vue ^3.3 | ≤ 4 КБ (ядро) |
@macrulez/vue-state-machine/devtools | vue ^3.3 | отдельный чанк |
- Поставляется как tree-shakeable ESM (
dist/index.mjs) и CommonJS (dist/index.cjs) "sideEffects": falseвpackage.json— бандлеры могут удалять неиспользуемые экспорты- Точка входа
/devtools— отдельный чанк; импорт внутри блокаif (import.meta.env.DEV)гарантирует исключение из продакшен-бандлов стандартным tree-shaking'ом
Разработка
bash
git clone https://github.com/macrulezru/vue-state-machine.git
cd vue-state-machine
npm install
npm run build # vite build
npm test # vitest run
npm run typecheck # tsc --noEmit
npm run lint # eslint --fixЛицензия
MIT