# 演员 Actors 4.6+
演员(Actor)模型 (opens new window) 是一种基于消息的计算的数学模型,它简化了多个“实体”(或“演员”)相互通信的方式。 演员通过相互发送消息(事件)来进行通信。 演员 的本地状态是私有的,除非它希望通过将其作为事件发送来与另一个 演员 共享。
当一个 演员 收到一个事件时,会发生三件事:
- 有限数量的消息可以 sent 给其他 演员
- 可以创建(或 spawned)有限数量的新演员
- 演员 的本地状态可能会改变(由其 behavior 决定)
状态机和状态图与 演员 模型配合得很好,因为它们是基于事件的行为和逻辑模型。 请记住:当状态机因事件而转换时,下一个状态包含:
- 下一个
value
和context
( 演员 的本地状态) - 要执行的下一个
actions
(可能是新生成的 演员 或发送给其他 演员 的消息)
演员 可以是 创建的 或 调用的。 创建的 演员 与调用的 演员 有两个主要区别:
- 他们可以在任何时候被 创建(通过
assign(...)
操作中的spawn(...)
) - 他们可以随时 停止(通过
stop(...)
动作)
# 演员 API
演员 XState 中实现)具有以下接口:
- 一个
id
属性,它在本地系统中唯一标识角色 - 一个
.send(...)
方法,用于向这个 演员 发送事件 - 一个
.getSnapshot()
方法,同步返回演员的最后 触发值。
他们可能有可选的方法:
- 一个
.stop()
方法,它停止 演员 并执行任何必要的清理 - Observable (opens new window) 的 演员 的
.subscribe(...)
方法。
所有现有的调用服务模式都适合这个接口:
- 调用 promises 是忽略任何接收到的事件并最多将一个事件发送回父级的演员
- 调用 callbacks 是可以向父级发送事件的演员
(第一个
callback
参数), 接收事件(第二个onReceive
参数),并对它们采取动作 - 调用 machines 是可以将事件发送到父级(
sendParent(...)
动作)或它引用的其他演员(send(...)
动作)、接收事件、对它们采取行动(状态转换和动作)的 演员 ),产生新的 演员(spawn(...)
函数),并停止 演员。 - 调用 observables 是其发出的值表示要发送回父级的事件的 演员。
什么是触发的值?
演员的 触发值 是订阅者在 演员 的.subscribe(...)
方法中收到的值。
- 对于 service,发出当前状态。
- 对于 promise,发出 resolve 的值(如果未实现,则为“未定义”)。
- 对于 observables,发出最新发出的值。
- 对于 callback,不会发出任何内容。
# 创建演员
就像在基于 演员 模型的语言中一样 Akka (opens new window) 或 Erlang (opens new window),演员 在context
中产生并被引用(作为assign(...)
操作的结果)。
- 从
'xstate'
导入spawn
函数 - 在
assign(...)
动作中,使用spawn(...)
创建一个新的 演员 引用
spawn(...)
函数通过提供 1 或 2 个参数来创建 演员 引用:
entity
- 代表 演员 东走的(反应)值或状态机。entity
的可能类型:name
(可选) - 唯一标识 演员 的字符串。 这对于所有生成的 演员 和调用的服务应该是唯一的。
或者,spawn
接受一个选项对象作为第二个参数,它可能包含以下选项:
name
(可选) - 唯一标识演员的字符串。 这对于所有生成的演员和调用的服务应该是唯一的。autoForward
- (可选)true
如果发送到这台状态机的所有事件也应该发送(或 转发)到被调用的子节点(默认情况下为false
)sync
- (可选)true
如果这台状态机应该自动订阅产生的子状态机的状态,状态将在子状态机 ref 上存储为.state
import { createMachine, spawn } from 'xstate';
import { todoMachine } from './todoMachine';
const todosMachine = createMachine({
// ...
on: {
'NEW_TODO.ADD': {
actions: assign({
todos: (context, event) => [
...context.todos,
{
todo: event.todo,
// 添加一个具有唯一名称的新 todoMachine actor
ref: spawn(todoMachine, `todo-${event.id}`)
}
]
})
}
// ...
}
});
如果你没有为 spawn(...)
提供 name
参数,将会自动生成一个唯一的名称。 此名称将是不确定的⚠️。
提示
将 const actorRef = spawn(someMachine)
视为 context
中的一个普通值。 你可以根据你的逻辑要求将此 actorRef
放置在 context
内的任何位置。 只要它在assign(...)
中的赋值函数内,它就会被限定在它产生的服务范围内。
注意
不要在赋值函数之外调用 spawn(...)
。 这将产生一个没有效果的孤儿演员(没有父级)。
// ❌ 永远不要在外部调用 spawn(...)
const someActorRef = spawn(someMachine);
// ❌ spawn(...) 不是action创建者
{
actions: spawn(someMachine);
}
// ❌ 不要在赋值函数之外赋值 spawn(...)
{
actions: assign({
// 记住:这是在服务启动之前立即调用的
someActorRef: spawn(someMachine)
});
}
// ✅ 在赋值函数中分配 spawn(...)
{
actions: assign({
someActorRef: () => spawn(someMachine)
});
}
可以生成不同类型的值作为演员。
# 发送事件到演员
使用 send()
动作,事件可以通过 目标表达式 发送给演员:
const machine = createMachine({
// ...
states: {
active: {
entry: assign({
someRef: () => spawn(someMachine)
}),
on: {
SOME_EVENT: {
// 使用目标表达式将事件发送到演员引用
actions: send({ type: 'PING' }, { to: (context) => context.someRef })
}
}
}
}
});
提示
如果你为 spawn(...)
提供了一个唯一的 name
参数,你可以在目标表达式中引用它:
const loginMachine = createMachine({
// ...
entry: assign({
formRef: () => spawn(formMachine, 'form')
}),
states: {
idle: {
on: {
LOGIN: {
actions: send({ type: 'SUBMIT' }, { to: 'form' })
}
}
}
}
});
# 停止演员
使用 stop(...)
动作创建器停止演员:
const someMachine = createMachine({
// ...
entry: [
// 通过引用停止一个actor
stop((context) => context.someActorRef),
// 通过 ID 停止 actor
stop('some-actor')
]
});
# 创建 Promises
就像 invoking promises 一样,promise 可以作为 演员 生成。 发送回状态机的事件将是一个 'done.invoke.<ID>'
操作,promise 响应作为有效负载中的 data
属性:
// Returns a promise
const fetchData = (query) => {
return fetch(`http://example.com?query=${event.query}`).then((data) =>
data.json()
);
};
// ...
{
actions: assign({
ref: (_, event) => spawn(fetchData(event.query))
});
}
// ...
注意
不建议生成promise 演员,因为 调用 promises 是一种更好的模式,因为它们依赖于状态(自我取消)并且具有更可预测的行为。
# 创建 Callbacks
就像 调用 callbacks 一样,回调可以作为 演员 产生。 这个例子模拟了一个 定时计数 演员,它每秒增加自己的计数,但也可以对 { type: 'INC' }
事件做出反应。
const counterInterval = (callback, receive) => {
let count = 0;
const intervalId = setInterval(() => {
callback({ type: 'COUNT.UPDATE', count });
count++;
}, 1000);
receive(event => {
if (event.type === 'INC') {
count++;
}
});
return () => { clearInterval(intervalId); }
}
const machine = createMachine({
// ...
{
actions: assign({
counterRef: () => spawn(counterInterval)
})
}
// ...
});
然后可以将事件发送给演员:
const machine = createMachine({
// ...
on: {
'COUNTER.INC': {
actions: send({ type: 'INC' }, { to: (context) => context.counterRef })
}
}
// ...
});
# 创建 Observables
就像 调用 observables 一样,observables 可以作为 演员 生成:
import { interval } from 'rxjs';
import { map } from 'rxjs/operators';
const createCounterObservable = (ms) => interval(ms)
.pipe(map(count => ({ type: 'COUNT.UPDATE', count })))
const machine = createMachine({
context: { ms: 1000 },
// ...
{
actions: assign({
counterRef: ({ ms }) => spawn(createCounterObservable(ms))
})
}
// ...
on: {
'COUNT.UPDATE': { /* ... */ }
}
});
# 创建状态机
状态机是使用 演员 的最有效方式,因为它们提供了最多的功能。 生成状态机就像 调用 状态机,其中一个 machine
被传递到 spawn(machine)
:
const remoteMachine = createMachine({
id: 'remote',
initial: 'offline',
states: {
offline: {
on: {
WAKE: 'online'
}
},
online: {
after: {
1000: {
actions: sendParent('REMOTE.ONLINE')
}
}
}
}
});
const parentMachine = createMachine({
id: 'parent',
initial: 'waiting',
context: {
localOne: null
},
states: {
waiting: {
entry: assign({
localOne: () => spawn(remoteMachine)
}),
on: {
'LOCAL.WAKE': {
actions: send({ type: 'WAKE' }, { to: (context) => context.localOne })
},
'REMOTE.ONLINE': { target: 'connected' }
}
},
connected: {}
}
});
const parentService = interpret(parentMachine)
.onTransition((state) => console.log(state.value))
.start();
parentService.send({ type: 'LOCAL.WAKE' });
// => 'waiting'
// ... after 1000ms
// => 'connected'
# 同步和读取 State 4.6.1+
演员 模型的主要原则之一是, 演员 状态是 私有的 和 本地的,它永远不会共享,除非 演员 选择通过消息传递来共享它。 坚持使用这个模型,演员 可以在其状态发生变化时,通过向其发送具有最新状态的特殊“更新”事件,来 通知 其父级。 换句话说,父演员 可以订阅其子演员 的状态。
为此,请将 { sync: true }
设置为 spawn(...)
的选项:
// ...
{
actions: assign({
// 每当其状态发生变化时,Actor 都会向父级发送更新事件
someRef: () => spawn(todoMachine, { sync: true })
});
}
// ...
这将自动为状态机订阅生成的子状态机的状态,该状态会保持更新并可通过 getSnapshot()
访问:
someService.onTransition((state) => {
const { someRef } = state.context;
console.log(someRef.getSnapshot());
// => State {
// value: ...,
// context: ...
// }
});
someService.onTransition((state) => {
const { someRef } = state.context;
console.log(someRef.state);
// => State {
// value: ...,
// context: ...
// }
});
注意
默认情况下,sync
设置为 false
。 当禁用sync
时,永远不要读取演员的.state
; 否则,你最终将引用陈旧的状态。
# 发送更新 4.7+
对于不与父级同步的 演员,演员 可以通过 sendUpdate()
向其父状态机发送显式事件:
import { createMachine, sendUpdate } from 'xstate';
const childMachine = createMachine({
// ...
on: {
SOME_EVENT: {
actions: [
// ...
// 创建一个将更新事件发送给父级的操作
sendUpdate()
]
}
}
});
提示
更喜欢显式地向父级发送事件(sendUpdate()
),而不是订阅每个状态更改。 与生成的状态机同步可能会导致“闲聊”事件日志,因为来自子级的每次更新都会导致从子级发送到父级的新“xstate.update”事件。
# 快速参考
导入 spawn
并创建演员:
import { spawn } from 'xstate';
在 assign
动作中 创建演员 :
// ...
{
actions: assign({
someRef: (context, event) => spawn(someMachine)
});
}
// ...
创建不同类型 的演员:
// ...
{
actions: assign({
// 来自 promise
promiseRef: (context, event) =>
spawn(
new Promise((resolve, reject) => {
// ...
}),
'my-promise'
),
// 来自callback
callbackRef: (context, event) =>
spawn((callback, receive) => {
// 发送到父级
callback('SOME_EVENT');
// 接收父级
receive((event) => {
// 处理 event
});
// 处理
return () => {
/* 在这里做清理 */
};
}),
// 来自 observable
observableRef: (context, event) => spawn(someEvent$),
// 来自machine
machineRef: (context, event) =>
spawn(
createMachine({
// ...
})
)
});
}
// ...
与 演员 同步状态:
// ...
{
actions: assign({
someRef: () => spawn(someMachine, { sync: true })
});
}
// ...
从 演员 那里 获取快照:4.20.0+
service.onTransition((state) => {
const { someRef } = state.context;
someRef.getSnapshot();
// => State { ... }
});
使用 send
动作创建者向演员发送事件:
// ...
{
actions: send(
{ type: 'SOME_EVENT' },
{
to: (context) => context.someRef
}
);
}
// ...
使用 send
表达式 将带有数据的事件发送给演员:
// ...
{
actions: send((context, event) => ({ ...event, type: 'SOME_EVENT' }), {
to: (context) => context.someRef
});
}
// ...
使用 sendParent
动作创建者将事件从演员发送到父级:
// ...
{
actions: sendParent({ type: 'ANOTHER_EVENT' });
}
// ...
使用 sendParent
表达式将带有数据的事件从演员发送到父级:
// ...
{
actions: sendParent((context, event) => ({
...context,
type: 'ANOTHER_EVENT'
}));
}
// ...
从 context
查看演员 :
someService.onTransition((state) => {
const { someRef } = state.context;
console.log(someRef);
// => { id: ..., send: ... }
});
← 调用 Services 延迟事件 和 转换 →