Акторы¶
Начиная с версии 4.6+
Модель актора — это математическая модель вычислений на основе сообщений, которая упрощает взаимодействие нескольких «сущностей» (или «акторов») друг с другом. Акторы общаются, отправляя друг другу сообщения (события). Локальное состояние актора является частным, если только он не хочет поделиться им с другим актором, отправив его как событие.
Когда актор получает событие, могут произойти три вещи:
- Конечное количество сообщений может быть отправлено другим участникам.
- Может быть создано (или создано) конечное количество новых участников.
- Локальное состояние актора может измениться (определяется его поведением)
Конечные автоматы и диаграммы состояний очень хорошо работают с моделью акторов, поскольку они являются основанными на событиях моделями поведения и логики. Помните: когда конечный автомат переходит из-за события, следующее состояние содержит:
- Следующее значение
value
и контекстcontext
(локальное состояние актора) - Следующие действия
actions
, которые должны быть выполнены (потенциально новые созданные акторы или сообщения, отправленные другим акторам)
Акторов можно создавать или вызывать. Созданные акторы имеют два основных отличия от вызванных:
- Они могут быть созданы в любое время (через
spawn(...)
внутри действияassign(...)
) - Их можно остановить в любой момент (с помощью действия
stop(...)
)
API акторов¶
Актор (как реализовано в XState) имеет интерфейс:
- Свойство
id
, которое однозначно идентифицирует актора в локальной системе - Метод
.send(...)
, который используется для отправки событий этому актору - Метод
.getSnapshot()
, который синхронно возвращает последнее переданное значение актора.
У акторов могут быть дополнительные методы:
- Метод
.stop()
, который останавливает актора и выполняет всю необходимую очистку - Метод
.subscribe(...)
для наблюдаемых акторов.
Все существующие шаблоны вызываемых сервисов подходят под этот интерфейс:
- Вызванные промисы — это акторы, которые игнорируют любые полученные события и отправляют не более одного события обратно родительскому объекту.
- Вызванные обратные вызовы — это акторы, которые могут отправлять события родительскому объекту (первый аргумент
callback
), получать события (второй аргументonReceive
) и действовать в соответствии с ними. - Вызываемые наблюдаемые объекты — это акторы, чьи передаваемые значения представляют собой события, которые должны быть отправлены обратно родительскому объекту.
- Вызванные машины — это акторы, которые могут отправлять события родительскому объекту (действие
sendParent(...)
) или другим субъектам, на которые он ссылается (действиеsend(...)
), получать события, действовать на них (переходы между состояниями и действия), создавать новых акторов (функцияspawn(...)
) и останавливать их.
Что такое эмитированное значение?
Эмитированное значение (emitted value) актора — это значение, которое подписчики получают в методе актора .subscribe(...)
.
- Для служб передается текущее состояние.
- Для промисов — разрешенное значение (или
undefined
, если не выполнено). - Для наблюдаемых объектов — последнее переданное значение.
- Для обратных вызовов ничего не эмитируется.
Создание акторов¶
Так же, как в языках, основанных на модели акторов, таких как Akka или Erlang, акторы создаются и на них ссылаются в контексте context
(в результате действия assign(...)
).
- Импортируйте функцию
spawn
изxstate
- В действии
assign(...)
создайте новую ссылку на актора с помощьюspawn(...)
Функция spawn(...)
создает ссылку на актора, запрашивая 1 или 2 аргумента:
-
entity
— (реактивное) значение или автомат, который представляет поведение актора. Возможные типыentity
:
name
(необязательно) — строка, однозначно определяющая актора. Она должна быть уникальной для всех созданных акторов и вызванных служб.
В качестве альтернативы spawn
принимает объект параметров в качестве второго аргумента, который может содержать следующие свойства:
name
(необязательно) — строка, однозначно определяющая актора. Он должен быть уникальным для всех созданных акторов и вызванных служб.autoForward
— (необязательно)true
, если все события, отправленные на этот автомат, также должны быть отправлены (или перенаправлены) вызванному дочернему элементу (по умолчаниюfalse
)sync
— (необязательно)true
, если этот автомат должен автоматически подписываться на состояние порожденной дочерней машины, состояние будет сохранено как.state
на дочерней машинеref
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
Если вы не предоставите аргумент name
для spawn(...)
, уникальное имя будет автоматически сгенерировано. Это имя будет недетерминированным.
Подсказка
Рассматривайте const actorRef = spawn(someMachine)
как обычное значение в context
. Вы можете разместить этот actorRef
где угодно в context
, в зависимости от ваших логических требований. Пока он находится в функции присваивания в assign(...)
, он будет привязан к службе, из которой он был создан.
Внимание
Не вызывайте spawn(...)
вне функции присваивания. Это приведет к появлению осиротевшего актора (без родителя), который не будет иметь никакого эффекта.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
В качестве акторов могут быть созданы различные типы значений.
Отправка событий акторам¶
С помощью действия send()
события можно отправлять акторам через целевое выражение:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Подсказка
Если вы передаете аргумент с уникальным name
для spawn(...)
, вы можете ссылаться на него в целевом выражении:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Остановка акторов¶
Акторы могут быть остановлены с помощью создателя действия stop(...)
:
1 2 3 4 5 6 7 8 9 |
|
Создание промисов¶
Как и при вызове промисов, промисы могут создаваться как акторы. Событие, отправленное обратно на автомат, будет действием done.invoke.<ID>
с промисом в качестве свойства data
в полезной нагрузке:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Внимание
Создавать акторы промисов не рекомендуется, поскольку вызов промисов — лучший паттерн для этого, т. к. они зависят от состояния (самоотменяемы) и имеют более предсказуемое поведение.
Создание функций обратного вызова¶
Как и при вызове функций обратного вызова, обратные вызовы могут быть созданы как акторы. В этом примере моделируется субъект счетчика интервалов, который увеличивает свой счет каждую секунду, а также может реагировать на события {type: 'INC'}
.
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 |
|
Создание "наблюдаемых"¶
Как и при вызове наблюдаемых объектов, наблюдаемые объекты могут быть созданы как акторы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Создание автоматов¶
Автоматы — это наиболее эффективный способ использования акторов, поскольку они предлагают наибольшие возможности. Создавать автоматы можно так же, как вызывать автоматы, когда machine
передается в spawn(machine)
:
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 42 43 44 45 46 47 48 49 50 51 52 |
|
Синхронизация и считывание состояния¶
Начиная с версии 4.6.1+
Один из основных принципов модели актора заключается в том, что состояние актора является приватным и локальным — оно никогда не передается, если актор не решает поделиться им посредством передачи сообщений. Придерживаясь этой модели, актор может уведомлять своего родителя всякий раз, когда его состояние изменяется, отправляя ему специальное событие «update
» с его последним состоянием. Другими словами, родительские акторы могут подписаться на состояния своих дочерних акторов.
Для этого установите {sync: true}
в качестве опции для srawn(...)
:
1 2 3 4 5 6 7 8 |
|
Это автоматически подпишет автомат на состояние порожденного дочернего автомата, которое постоянно обновляется и может быть доступно через getSnapshot()
:
1 2 3 4 5 6 7 8 9 |
|
1 2 3 4 5 6 7 8 9 |
|
Внимание
По умолчанию для sync
установлено значение false
. Никогда не читайте .state
актора, когда sync
отключена; в противном случае вы получите ссылку на устаревшее состояние.
Отправка обновлений¶
Начиная с версии 4.7+
Для акторов, которые не синхронизированы с родительским, актор может отправить явное событие на свой родительский автомат через sendUpdate()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Подсказка
Предпочитайте явно отправлять события родительскому автомату (sendUpdate()
), а не подписываться на каждое изменение состояния. Синхронизация с созданными машинами может привести к появлению «болтливых» журналов событий, поскольку каждое обновление от дочернего элемента приводит к новому событию xstate.update
, отправляемому дочерним автоматом родительскому.
Краткий справочник¶
Импорт spawn
для вызова актора:
1 |
|
Вызов акторов в создателе действия assign
:
1 2 3 4 5 6 7 |
|
Вызов различных типов акторов:
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 42 |
|
Статус синхронизации актора:
1 2 3 4 5 6 7 |
|
Получение снапшота актора (4.20.0+):
1 2 3 4 5 6 |
|
Отправить событие актору с помощью создателя действия send
:
1 2 3 4 5 6 7 8 9 10 |
|
Отправить событие с данными актору, используя выражение send
:
1 2 3 4 5 6 7 8 9 10 |
|
Отправить событие от актора к родительскому автомату с создателем действия sendParent
:
1 2 3 4 5 |
|
Отправить событие с данными от актора к родительскому автомату с помощью выражения sendParent
:
1 2 3 4 5 6 7 8 |
|
Ссылка на акторов из context
:
1 2 3 4 5 6 |
|