Акторы
Когда вы запускаете конечный автомат, он становится актором: работающим процессом, который может получать события, отправлять события и изменять своё поведение на основе получаемых событий, что может вызывать эффекты за пределами актора.
В конечных автоматах акторы могут быть вызваны или порождены. По сути это одно и то же, с единственным различием в том, как контролируется жизненный цикл актора.
- Вызванный актор запускается, когда его родительский автомат входит в состояние, в котором он вызван, и останавливается при выходе из этого состояния.
- Порождённый актор запускается в переходе и останавливается либо с помощью действия
stop(...), либо при остановке родительского автомата.
Модель акторов¶
В модели акторов акторы — это объекты, которые могут взаимодействовать друг с другом. Они являются независимыми «живыми» сущностями, которые общаются посредством асинхронной передачи сообщений. В XState эти сообщения называются событиями.
- Актор имеет собственное внутреннее, инкапсулированное состояние, которое может обновляться только самим актором. Актор может обновить своё внутреннее состояние в ответ на полученное сообщение, но оно не может быть обновлено никакой другой сущностью.
- Акторы общаются с другими акторами, отправляя и получая события асинхронно.
- Акторы обрабатывают по одному сообщению за раз. Они имеют внутренний «почтовый ящик», который действует как очередь событий, обрабатывая события последовательно.
- Внутреннее состояние актора не разделяется между акторами. Единственный способ для актора поделиться частью своего внутреннего состояния:
- Отправка событий другим акторам
- Или генерация снимков (
snapshots), которые можно рассматривать как неявные события, отправляемые подписчикам.
- Акторы могут создавать (
spawn/invoke) новых акторов.
Логика актора¶
Логика актора — это логическая «модель» актора (мозг, чертёж, ДНК и т.д.). Она описывает, как актор должен изменять поведение при получении события. Вы можете создавать логику актора с помощью создателей логики актора.
В XState логика актора определяется объектом, реализующим интерфейс ActorLogic, содержащим методы, такие как .transition(...), .getInitialSnapshot(), .getPersistedSnapshot() и другие. Этот объект сообщает интерпретатору, как обновлять внутреннее состояние актора при получении события и какие эффекты выполнять (если есть).
Создание акторов¶
Вы можете создать актор, который является «живым» экземпляром некоторой логики актора, с помощью createActor(actorLogic, options?). Функция createActor(...) принимает следующие аргументы:
actorLogic: логика актора для создания актораoptions(необязательно): опции актора
Когда вы создаёте актор из логики актора через createActor(actorLogic), вы неявно создаёте систему акторов, где созданный актор является корневым актором. Любые акторы, порождённые из этого корневого актора и его потомков, являются частью этой системы акторов. Актор должен быть запущен вызовом actor.start(), что также запустит систему акторов:
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Вы можете остановить корневых акторов, вызвав actor.stop(), что также остановит систему акторов и всех акторов в этой системе:
1 2 | |
Вызов и порождение акторов¶
Вызванный актор представляет актор на основе состояния, поэтому он останавливается при выходе из вызывающего состояния. Вызванные акторы используются для конечного/известного количества акторов.
Порождённый актор представляет множество сущностей, которые могут быть запущены в любое время и остановлены в любое время. Порождённые акторы основаны на действиях и используются для динамического или неизвестного количества акторов.
Примером различия между вызовом и порождением акторов может быть приложение для задач. При загрузке задач актор loadTodos будет вызванным актором; он представляет одну задачу на основе состояния. Для сравнения, каждая из задач сама может быть порождённым актором, и их может быть динамическое количество.
Снимки актора¶
Когда актор получает событие, его внутреннее состояние может измениться. Актор может генерировать снимок при переходе состояния. Вы можете синхронно прочитать снимок актора через actor.getSnapshot() или подписаться на снимки через actor.subscribe(observer).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | |
Подписки¶
Вы можете подписаться на значения снимков актора через actor.subscribe(observer). Наблюдатель будет получать значение снимка актора при его генерации. Наблюдатель может быть:
- Простой функцией, которая получает последний снимок, или
- Объектом-наблюдателем, метод
.next(snapshot)которого получает последний снимок
1 2 3 4 | |
1 2 3 4 5 6 7 8 9 10 11 12 | |
Возвращаемое значение actor.subscribe(observer) — это объект подписки, у которого есть метод .unsubscribe(). Вы можете вызвать subscription.unsubscribe() для отписки наблюдателя:
1 2 3 4 5 6 | |
Когда актор останавливается, все его наблюдатели будут автоматически отписаны.
Вы можете инициализировать логику актора в определённом сохранённом снимке (состоянии), передав состояние во втором аргументе options функции createActor(logic, options). Если состояние совместимо с логикой актора, это создаст актор, который будет запущен в этом сохранённом состоянии:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
См. персистентность для получения дополнительной информации.
waitFor¶
Вы можете дождаться, пока снимок актора удовлетворит предикат, используя вспомогательную функцию waitFor(actor, predicate, options?). Функция waitFor(...) возвращает промис, который:
- Разрешается, когда сгенерированный снимок удовлетворяет функцию
predicate - Разрешается немедленно, если текущий снимок уже удовлетворяет функцию
predicate - Отклоняется, если возникает ошибка или истекает значение
options.timeout.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
Обработка ошибок¶
Вы можете подписаться на ошибки, выбрасываемые актором, используя колбэк error в объекте-наблюдателе, передаваемом в actor.subscribe(). Это позволяет обрабатывать ошибки, генерируемые логикой актора.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
Создатели логики актора¶
Типы логики актора, которые вы можете создать из XState:
- Логика конечного автомата (
createMachine(...)) - Логика промиса (
fromPromise(...)) - Логика функции перехода (
fromTransition(...)) - Логика Observable (
fromObservable(...)) - Логика Event Observable (
fromEventObservable(...)) - Логика колбэка (
fromCallback(...))
Возможности логики актора¶
| Получение событий | Отправка событий | Порождение акторов | Input | Output | |
|---|---|---|---|---|---|
| Акторы конечных автоматов | ✅ | ✅ | ✅ | ✅ | ✅ |
| Promise-акторы | ❌ | ✅ | ❌ | ✅ | ✅ |
| Transition-акторы | ✅ | ✅ | ❌ | ✅ | ❌ |
| Observable-акторы | ❌ | ✅ | ❌ | ✅ | ❌ |
| Callback-акторы | ✅ | ✅ | ❌ | ✅ | ❌ |
Логика конечного автомата (createMachine(...))¶
Вы можете описать логику актора как конечный автомат. Акторы, созданные из логики актора конечного автомата, могут:
- Получать события
- Отправлять события другим акторам
- Вызывать/порождать дочерних акторов
- Генерировать снимки своего состояния
- Выводить значение, когда автомат достигает финального состояния верхнего уровня
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | |
Узнайте больше об акторах конечных автоматов.
Логика промиса (fromPromise(...))¶
Логика promise-актора описывается асинхронным процессом, который разрешается или отклоняется через некоторое время. Акторы, созданные из логики промиса («promise-акторы»), могут:
- Генерировать разрешённое значение промиса
- Выводить разрешённое значение промиса
Отправка событий promise-акторам не будет иметь эффекта.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
Узнайте больше о promise-акторах.
Логика функции перехода (fromTransition(...))¶
Логика transition-актора описывается функцией перехода, похожей на редьюсер. Функции перехода принимают текущее state и полученный объект event в качестве аргументов и возвращают следующее состояние. Акторы, созданные из логики перехода («transition-акторы»), могут:
- Получать события
- Генерировать снимки своего состояния
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | |
Узнайте больше о transition-акторах.
Логика Observable (fromObservable(...))¶
Логика observable-актора описывается потоком наблюдаемых значений. Акторы, созданные из логики observable («observable-акторы»), могут:
- Генерировать снимки сгенерированного значения observable
Отправка событий observable-акторам не будет иметь эффекта.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
Узнайте больше об observable-акторах.
Логика Event Observable (fromEventObservable(...))¶
Логика event observable-актора описывается потоком объектов событий. Акторы, созданные из логики event observable («event observable-акторы»), могут:
- Неявно отправлять события родительскому актору
- Генерировать снимки сгенерированных объектов событий
Отправка событий event observable-акторам не будет иметь эффекта.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
Узнайте больше об observable-акторах.
Логика колбэка (fromCallback(...))¶
Логика callback-актора описывается функцией колбэка, которая получает один объект-аргумент, включающий функцию sendBack(event) и функцию receive(event => ...). Акторы, созданные из логики колбэка («callback-акторы»), могут:
- Получать события через функцию
receive - Отправлять события родительскому актору через функцию
sendBack
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | |
Callback-акторы немного отличаются от других акторов тем, что они не делают следующее:
- Не работают с
onDone - Не создают снимок с помощью
.getSnapshot() - Не генерируют значения при использовании с
.subscribe()
Вы можете использовать sendBack для сообщения о пойманных ошибках родительскому актору. Это особенно полезно для обработки отклонений промисов внутри функции колбэка, которые не будут перехвачены onError.
Функции колбэка не могут быть async функциями. Но можно выполнить промис внутри функции колбэка.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | |
Узнайте больше о callback-акторах.
Акторы как промисы¶
Вы можете создать промис из любого актора, используя функцию toPromise(actor). Промис разрешится с .output снимка актора, когда актор завершён (snapshot.status === 'done'), или отклонится с .error снимка актора, когда актор в ошибке (snapshot.status === 'error').
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | |
Если актор уже завершён, промис разрешится с snapshot.output актора немедленно. Если актор уже в ошибке, промис отклонится с snapshot.error актора немедленно.
Логика актора высшего порядка¶
Логика актора высшего порядка расширяет существующую логику актора дополнительной функциональностью. Например, вы можете создать логику актора, которая логирует или сохраняет состояние актора:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | |
Пользовательская логика актора¶
Пользовательская логика актора может быть определена с помощью объекта, реализующего интерфейс ActorLogic.
Например, вот пользовательский объект логики актора с функцией transition, которая работает как простой редьюсер:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | |
Для дополнительных примеров см. реализации ActorLogic в исходном коде, например создатель логики актора fromTransition, или примеры в тестах.
Пустые акторы¶
Актор, который ничего не делает и имеет только один сгенерированный снимок: undefined
В XState пустой актор — это актор, который ничего не делает и имеет только один сгенерированный снимок: undefined.
Это полезно для тестирования, например для заглушки актора, который ещё не реализован. Это также может быть полезно в интеграциях с фреймворками, таких как @xstate/react, где актор может быть ещё недоступен:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
Акторы и TypeScript¶
TypeScript
XState v5 требует TypeScript версии 5.0 или выше.
Для лучших результатов используйте последнюю версию TypeScript. Подробнее о XState и TypeScript
Вы можете строго типизировать actors вашего автомата в свойстве types.actors конфигурации автомата.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | |
Тестирование¶
Общая стратегия тестирования акторов — отправлять события и проверять, что актор достигает ожидаемого состояния, которое можно наблюдать либо:
- Подписываясь на его сгенерированные снимки через
actor.subscribe(...) - Или читая последний снимок через
actor.getSnapshot().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | |
Шпаргалка по акторам¶
Шпаргалка: создание актора¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
Шпаргалка: логика конечного автомата¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
Шпаргалка: логика промиса¶
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Шпаргалка: логика функции перехода¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | |
Шпаргалка: логика Observable¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
Шпаргалка: логика Event Observable¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | |
Шпаргалка: логика колбэка¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | |