Состояния¶
Состояние (state) — это абстрактное представление системы (например, приложения) в определенный момент времени.
API¶
Текущее состояние машины представлено экземпляром State
:
const lightMachine = createMachine({
id: 'light',
initial: 'green',
states: {
green: {
/* ... */
},
// ...
},
});
console.log(lightMachine.initialState);
// State {
// value: 'green',
// actions: [],
// context: undefined,
// // ...
// }
console.log(
lightMachine.transition('yellow', { type: 'TIMER' })
);
// State {
// value: { red: 'walk' },
// actions: [],
// context: undefined,
// // ...
// }
Определение состояния¶
Экземпляр объекта State
является сериализуемым в формате JSON и имеет следующие свойства:
value
— текущее значение состояния (например,{red: 'walk'}
)context
— текущее значение контекста этого состоянияevent
— объект события, вызвавший переход в это состояниеactions
— массив действий, которые нужно выполнитьactivities
— отображение активностей вtrue
, если активность запущена или вfalse
, если остановлена.history
— предыдущее состояниеState
meta
— любая статичная метаинформация, определенная в свойствеmeta
узла состоянияdone
— признак конечного состояния
Объект State
также содержит другие свойства, такие как historyValue
, events
, tree
и другие, которые обычно не имеют отношения к делу и предназначены для внутреннего использования.
Методы и свойства состояния¶
Вот несколько полезных методов и свойств, которые можно использовать для улучшения процесса разработки:
state.matches()
¶
Метод state.matches(parentStateValue)
определяет, является ли текущее значение state.value
подмножеством заданного parentStateValue
. Метод определяет, «совпадает» ли значение родительского состояния со значением состояния. Например, если текущее значение state.value
равно {red: 'stop'}
:
console.log(state.value);
// => { red: 'stop' }
console.log(state.matches('red'));
// => true
console.log(state.matches('red.stop'));
// => true
console.log(state.matches({ red: 'stop' }));
// => true
console.log(state.matches('green'));
// => false
Подсказка
Если вы хотите сравнить одно из нескольких состояний, вы можете использовать .some()
для массива значений состояния, чтобы выполнить это:
const isMatch = [
{ customer: 'deposit' },
{ customer: 'withdrawal' },
].some(state.matches);
state.nextEvents
¶
state.nextEvents
указывает следующие события, которые вызовут переход из текущего состояния:
const { initialState } = lightMachine;
console.log(initialState.nextEvents);
// => ['TIMER', 'EMERGENCY']
state.nextEvents
полезен для определения, какие следующие события могут быть выполнены, и представления этих потенциальных событий в пользовательском интерфейсе, таких как включение или отключение определенных кнопок.
state.changed
¶
state.changed
указывает, изменилось ли это состояние по сравнению с предыдущим состоянием. Состояние считается «измененным», если:
- Его значение не равно его предыдущему значению, или:
- У него есть какие-либо новые действия (побочные эффекты) для выполнения.
Исходное состояние (без истории) вернет undefined
.
const { initialState } = lightMachine;
console.log(initialState.changed);
// => undefined
const nextState = lightMachine.transition(initialState, {
type: 'TIMER',
});
console.log(nextState.changed);
// => true
const unchangedState = lightMachine.transition(nextState, {
type: 'UNKNOWN_EVENT',
});
console.log(unchangedState.changed);
// => false
state.done
¶
state.done
указывает, является ли состояние «конечным состоянием» — это состояние, которое указывает, что его автомат достиг своего конечного (терминального) состояния и больше не может переходить в какое-либо другое состояние.
const answeringMachine = createMachine({
initial: 'unanswered',
states: {
unanswered: {
on: {
ANSWER: { target: 'answered' },
},
},
answered: {
type: 'final',
},
},
});
const { initialState } = answeringMachine;
initialState.done; // false
const answeredState = answeringMachine.transition(
initialState,
{
type: 'ANSWER',
}
);
answeredState.done; // true
state.toStrings()
¶
Метод state.toStrings()
возвращает массив строк, представляющих все пути значений состояния. Например, если текущее значение state.value
равно {red: 'stop'}
:
console.log(state.value);
// => { red: 'stop' }
console.log(state.toStrings());
// => ['red', 'red.stop']
Метод state.toStrings()
полезен для представления текущего состояния в строковых средах, таких как классы CSS или атрибуты данных.
state.children
¶
state.children
— это объект, отображающий идентификаторы порожденных служб / акторов на их экземпляры. См. 📖 Referencing Services для более подробной информации.
Пример:
const machine = createMachine({
// ...
invoke: [
{ id: 'notifier', src: createNotifier },
{ id: 'logger', src: createLogger },
],
// ...
});
const service = invoke(machine)
.onTransition((state) => {
state.children.notifier; // service from createNotifier()
state.children.logger; // service from createLogger()
})
.start();
state.hasTag()
¶
Начиная с версии 4.19.0
Метод state.hasTag(tag)
определяет, есть ли в текущей конфигурации состояния узел состояния с данным тегом.
const machine = createMachine({
initial: 'green',
states: {
green: {
tags: 'go', // single tag
},
yellow: {
tags: 'go',
},
red: {
tags: ['stop', 'other'], // multiple tags
},
},
});
Например, если указанный выше автомат находится в зеленом или желтом состоянии, вместо прямого сопоставления состояния с помощью state.matches('green') || state.matches('yellow')
, можно использовать state.hasTag('go')
:
const canGo = state.hasTag('go');
// => `true` if in 'green' or 'yellow' state
state.can()
¶
Начиная с версии 4.25.0
Метод state.can(event)
определяет, вызовет ли событие изменение состояния, если оно будет отправлено на интерпретируемый автомат. Метод вернет истину, если состояние изменится из-за отправляемого события; иначе метод вернет false
:
const machine = createMachine({
initial: 'inactive',
states: {
inactive: {
on: {
TOGGLE: 'active',
},
},
active: {
on: {
DO_SOMETHING: { actions: ['something'] },
},
},
},
});
const inactiveState = machine.initialState;
inactiveState.can('TOGGLE'); // true
inactiveState.can('DO_SOMETHING'); // false
// Also takes in full event objects:
inactiveState.can({
type: 'DO_SOMETHING',
data: 42,
}); // false
const activeState = machine.transition(
inactiveState,
'TOGGLE'
);
activeState.can('TOGGLE'); // false
activeState.can('DO_SOMETHING'); // true, since an action will be executed
Состояние считается «измененным», если state.changed
true
и если выполняется любое из следующих условий:
- значение
state.value
изменилось - есть новые
state.actions
, которые нужно выполнить - значение
state.context
изменилось
Сохранение состояния¶
Как уже упоминалось, объект State
можно сохранить, сериализовав его в строковый формат JSON:
const jsonState = JSON.stringify(currentState);
// Example: persisting to localStorage
try {
localStorage.setItem('app-state', jsonState);
} catch (e) {
// unable to save to localStorage
}
Состояние можно восстановить с помощью статического метода State.create(...)
:
import { State, interpret } from 'xstate';
import { myMachine } from '../path/to/myMachine';
// Retrieving the state definition from localStorage,
// if localStorage is empty use machine initial state
const stateDefinition =
JSON.parse(localStorage.getItem('app-state')) ||
myMachine.initialState;
// Use State.create() to restore state from a plain object
const previousState = State.create(stateDefinition);
Затем вы можете интерпретировать автомат из этого состояния, передав State
в метод .start(...)
интерпретируемой службы:
// ...
// This will start the service at the specified State
const service = interpret(myMachine).start(previousState);
Это также будет поддерживать и восстанавливать предыдущие состояния истории и гарантирует, что .events
и .nextEvents
представляют правильные значения.
Внимание
Сохранение созданных акторов еще не поддерживается в XState.
Метаданные состояния¶
Мета-данные, которые представляют собой статические данные, описывающие соответствующие свойства любого узла состояния, могут быть указаны в свойстве .meta
узла состояния:
const fetchMachine = createMachine({
id: 'fetch',
initial: 'idle',
states: {
idle: {
on: { FETCH: { target: 'loading' } },
},
loading: {
after: {
3000: 'failure.timeout',
},
on: {
RESOLVE: { target: 'success' },
REJECT: { target: 'failure' },
TIMEOUT: { target: 'failure.timeout' }, // manual timeout
},
meta: {
message: 'Loading...',
},
},
success: {
meta: {
message: 'The request succeeded!',
},
},
failure: {
initial: 'rejection',
states: {
rejection: {
meta: {
message: 'The request failed.',
},
},
timeout: {
meta: {
message: 'The request timed out.',
},
},
},
meta: {
alert: 'Uh oh.',
},
},
},
});
Текущее состояние машины собирает .meta
данные всех узлов состояния, представленные значением состояния, и помещает их в объект, где:
- Ключи — это идентификаторы узлов состояния.
- Значения — это значения мета-узла состояния
.meta
.
Например, если вышеуказанный автомат находится в состоянии failure.timeout
(которое представлено двумя узлами состояния с идентификаторами «failure
» и «failure.timeout
»), свойство .meta
объединит все значения .meta
следующим образом:
const failureTimeoutState = fetchMachine.transition(
'loading',
{
type: 'TIMEOUT',
}
);
console.log(failureTimeoutState.meta);
// => {
// failure: {
// alert: 'Uh oh.'
// },
// 'failure.timeout': {
// message: 'The request timed out.'
// }
// }
Подсказка: Агрегирование метаданных
Что делать с метаданными — решать вам. В идеале метаданные должны содержать только значения, сериализуемые в формате JSON. Рассмотрите возможность слияния / агрегирования метаданных иначе. Например, следующая функция отбрасывает ключи идентификатора узла состояния (если они неактуальны) и объединяет метаданные:
function mergeMeta(meta) {
return Object.keys(meta).reduce((acc, key) => {
const value = meta[key];
// Assuming each meta value is an object
Object.assign(acc, value);
return acc;
}, {});
}
const failureTimeoutState = fetchMachine.transition(
'loading',
{
type: 'TIMEOUT',
}
);
console.log(mergeMeta(failureTimeoutState.meta));
// => {
// alert: 'Uh oh.',
// message: 'The request timed out.'
// }
Примечания¶
- Вам никогда не придется создавать экземпляр
State
вручную. СчитайтеState
объектом, доступным только для чтения, который поступает только отmachine.transition(...)
илиservice.onTransition(...)
. state.history
не сохраняет свою историю во избежание утечек памяти.state.history.history === undefined
. В противном случае вы создадите огромный связанный список и заново изобретете блокчейн, чего мы не собираемся делать.- Это поведение может быть изменено в будущих версиях.