Конечный автомат — это модель, описывающая поведение чего-либо, например актора. Конечные автоматы описывают, как состояние актора переходит в другое состояние при наступлении события.
В этом примере автомат имеет два состояния: question и thanks. Состояние question имеет переход в состояние thanks при отправке события feedback.good автомату:
Автомат содержит логику актора. Актор — это работающий экземпляр автомата; другими словами, это сущность, логика которой описывается автоматом. Из одного автомата можно создать несколько акторов, и каждый из этих акторов будет демонстрировать одинаковое поведение (реакцию на полученные события), но они будут независимы друг от друга и будут иметь собственные состояния.
Чтобы создать актора, используйте функцию createActor(machine):
Реализации автомата — это код на конкретном языке программирования, который выполняется, но не связан напрямую с логикой конечного автомата (состояния и переходы). Это включает:
Действия, которые являются побочными эффектами типа «запустил и забыл».
Акторы, которые являются сущностями, способными взаимодействовать с актором автомата.
Условия, которые определяют, должен ли переход быть выполнен.
Задержки, которые указывают время до выполнения отложенного перехода или отправки отложенного события.
Реализации по умолчанию можно предоставить в функции setup({...}) при создании автомата, а затем ссылаться на эти реализации с помощью JSON-сериализуемых строк и/или объектов, таких как { type: 'doSomething' }.
import{setup}from'xstate';constfeedbackMachine=setup({// Реализации по умолчаниюactions:{doSomething:()=>{console.log('Делаем что-то!');},},actors:{/* ... */},guards:{/* ... */},delays:{/* ... */},}).createMachine({entry:{type:'doSomething'},// ... остальная конфигурация автомата});constfeedbackActor=createActor(feedbackMachine);feedbackActor.start();// выводит 'Делаем что-то!'
Вы можете переопределить реализации по умолчанию, предоставив реализации через machine.provide(...). Эта функция создаст новый автомат с той же конфигурацией, но с предоставленными реализациями:
1 2 3 4 5 6 7 8 9101112
constcustomFeedbackMachine=feedbackMachine.provide({actions:{doSomething:()=>{console.log('Делаем что-то другое!');},},});constfeedbackActor=createActor(customFeedbackMachine);feedbackActor.start();// выводит 'Делаем что-то другое!'
Функция setup() предоставляет привязанные к типам помощники действий, которые полностью типизированы для context, events, actors, guards, delays и emitted типов setup. Эти помощники создают действия, привязанные к конкретному setup(), из которого они были созданы, и могут использоваться напрямую в автоматах, созданных этим setup.
import{setup}from'xstate';constmachineSetup=setup({types:{context:{}as{count:number;name:string},events:{}as|{type:'increment';value:number}|{type:'reset'},},});// Создание пользовательского действия с полной типобезопасностью// Может быть определено в любом файле, который импортирует machineSetup// highlight-startconstlogCount=machineSetup.createAction(({context,event})=>{// context и event полностью типизированыconsole.log(`Count: ${context.count}, Event: ${event.type}`);});// highlight-endconstmachine=machineSetup.createMachine({context:{count:0,name:'Counter'},initial:'counting',states:{counting:{entry:logCount,// Полностью типизированное действиеon:{increment:{actions:logCount,},},},},});
Setup предоставляет привязанные к типам версии всех основных встроенных действий:
setup(…).assign(…)
setup(…).raise(…)
setup(…).emit(…)
setup(…).sendTo(…)
setup(…).log(…)
setup(…).cancel(…)
setup(…).spawnChild(…)
setup(…).stopChild(…)
setup(…).enqueueActions(…)
Эти помощники полностью типизированы к типам вашего setup и не требуют объектов-обёрток:
import{setup}from'xstate';constmachineSetup=setup({types:{context:{}as{count:number;items:string[]},events:{}as|{type:'increment'}|{type:'addItem';item:string},emitted:{}as{type:'COUNT_CHANGED';count:number;},// ...},});// highlight-start// Привязанный к типу assign - context полностью типизированconstincrementCount=machineSetup.assign({count:({context})=>context.count+1,});constaddItem=machineSetup.assign({items:({context,event})=>[...context.items,event.item,],});// Привязанный к типу raise - события полностью типизированыconstraiseIncrement=machineSetup.raise({type:'increment',});// Привязанный к типу emit - emitted типы полностью типизированыconstemitCountChanged=machineSetup.emit(({context})=>({type:'COUNT_CHANGED',count:context.count,}));// Привязанный к типу sendTo - акторы полностью типизированыconstsendToLogger=machineSetup.sendTo('logger',({context})=>({type:'LOG',message:`Count is ${context.count}`,}));// Привязанный к типу log - context и события полностью типизированыconstlogContext=machineSetup.log(({context})=>`Context: ${JSON.stringify(context)}`);// Привязанный к типу cancel - акторы полностью типизированыconstcancelLogger=machineSetup.cancel('logger');// Привязанный к типу stopChild - акторы полностью типизированыconststopLogger=machineSetup.stopChild('logger');// Привязанный к типу spawnChild - акторы полностью типизированыconstspawnLogger=machineSetup.spawnChild('logger',{input:({context})=>({initialCount:context.count,}),});// Привязанный к типу enqueueActions - все помощники доступны с полной типизациейconstbatchActions=machineSetup.enqueueActions(({enqueue,check})=>{enqueue(incrementCount);enqueue(logContext);if(check(()=>true)){enqueue(emitCountChanged);}});// highlight-endconstmachine=machineSetup.createMachine({context:{count:0,items:[]},initial:'active',states:{active:{entry:[incrementCount,logContext,emitCountChanged,],on:{increment:{actions:[incrementCount,batchActions],},addItem:{actions:addItem,},},},},});
Когда вы создаёте актор конечного автомата, следующее состояние определяется текущим состоянием автомата и событием, отправленным актору. Однако вы также можете определить следующее состояние и действия из текущего состояния и события, используя чистые функции transition(machine, state, event) и initialTransition(machine):
Рекомендуется использовать функции initialTransition(…) и transition(…) вместо getNextSnapshot(…) и getInitialSnapshot(…), которые будут объявлены устаревшими.
Когда вы создаёте актор конечного автомата, следующее состояние определяется текущим состоянием автомата и событием, отправленным актору. Если вы хотите определить следующее состояние вне актора, вы можете использовать функцию getNextSnapshot(…):
Эти типы будут выводиться по всей конфигурации автомата и в созданном автомате и акторе, так что методы, такие как machine.transition(...) и actor.send(...), будут типобезопасными.
Вы можете использовать .createStateConfig(...) из setup API для создания модульных, переиспользуемых конфигураций состояний. Этот подход предоставляет несколько преимуществ, включая модульность, строгую типизацию и лучшую организацию.
import{setup}from'xstate';constlightMachineSetup=setup({// ...});// highlight-start// Создание отдельных конфигураций состоянийconstgreen=lightMachineSetup.createStateConfig({entry:{type:'startTimer'},on:{TIMER:{target:'yellow'},PEDESTRIAN:{target:'yellow'},EMERGENCY:{target:'red'},},});constyellow=lightMachineSetup.createStateConfig({entry:{type:'startTimer'},on:{TIMER:{target:'red'},EMERGENCY:{target:'red'},},});constred=lightMachineSetup.createStateConfig({entry:{type:'startTimer'},on:{TIMER:{target:'green'},EMERGENCY:{target:'green'},},});// highlight-end// Компоновка автомата с использованием модульных конфигураций состоянийconsttrafficLightMachine=lightMachineSetup.createMachine({initial:'green',states:{green,yellow,red,},});
Все конфигурации состояний, созданные с помощью .createStateConfig(...), имеют полные типы, указанные в конфигурации setup. Метод .createStateConfig(...) особенно полезен для очень больших, сложных конечных автоматов, где вы хотите разбить логику на управляемые части, сохраняя при этом строгую типизацию.
import{setup,fromPromise}from'xstate';constsomeAction=()=>{/* ... */};constsomeGuard=({context})=>context.count<=10;constsomeActor=fromPromise(async()=>{// ...return42;});constfeedbackMachine=setup({types:{context:{}as{count:number},events:{}as|{type:'increment'}|{type:'decrement'},},actions:{someAction,},guards:{someGuard,},actors:{someActor,},}).createMachine({initial:'counting',states:{counting:{entry:{type:'someAction'},// строго типизированоinvoke:{src:'someActor',// строго типизированоonDone:{actions:({event})=>{event.output;// строго типизировано как number},},},on:{increment:{guard:{type:'someGuard'},// строго типизированоactions:assign({count:({context})=>context.count+1,}),},},},},});