# 角色 4.6+

¥Actors 4.6+

🚀快速参考

¥🚀 Quick Reference

[[目录]]

¥

角色模型 (opens new window) 是基于消息的计算的数学模型,它简化了多个 "entities"(或 "actors")之间的通信方式。参与者通过向彼此发送消息(事件)来进行通信。Actor 的本地状态是私有的,除非它希望通过将其作为事件发送来与另一个 Actor 共享。

¥The Actor model (opens new window) is a mathematical model of message-based computation that simplifies how multiple "entities" (or "actors") communicate with each other. Actors communicate by sending messages (events) to each other. An actor's local state is private, unless it wishes to share it with another actor, by sending it as an event.

当参与者收到事件时,可能会发生三件事:

¥When an actor receives an event, three things can happen:

  • 可以将有限数量的消息发送给其他参与者

    ¥A finite number of messages can be sent to other actors

  • 可以创建(或产生)有限数量的新参与者

    ¥A finite number of new actors can be created (or spawned)

  • 演员的本地状态可能会改变(由其行为决定)

    ¥The actor's local state may change (determined by its behavior)

状态机和状态图与参与者模型配合得很好,因为它们是基于事件的行为和逻辑模型。记住:当状态机由于事件而转换时,下一个状态包含:

¥State machines and statecharts work very well with the actor model, as they are event-based models of behavior and logic. Remember: when a state machine transitions due to an event, the next state contains:

  • 接下来的 valuecontext(角色的本地状态)

    ¥The next value and context (an actor's local state)

  • 下一个要执行的 actions(可能是新生成的 Actor 或发送给其他 Actor 的消息)

    ¥The next actions to be executed (potentially newly spawned actors or messages sent to other actors)

可以生成 Actor 或 invoked。生成的 Actor 与调用的 Actor 有两个主要区别:

¥Actors can be spawned or invoked. Spawned actors have two major differences from invoked actors:

  • 它们可以随时生成(通过 assign(...) 动作中的 spawn(...)

    ¥They can be spawned at any time (via spawn(...) inside of an assign(...) action)

  • 它们可以随时停止(通过 stop(...) 动作)

    ¥They can be stopped at any time (via a stop(...) action)

# 角色 API

¥Actor API

参与者(在 XState 中实现)具有以下接口:

¥An actor (as implemented in XState) has an interface of:

  • id 属性,在本地系统中唯一标识参与者

    ¥An id property, which uniquely identifies the actor in the local system

  • .send(...) 方法,用于向此 actor 发送事件

    ¥A .send(...) method, which is used to send events to this actor

  • .getSnapshot() 方法,同步返回 actor 最后发出的值。

    ¥A .getSnapshot() method, which synchronously returns the actor's last emitted value.

他们可能有可选的方法:

¥They may have optional methods:

  • 一个 .stop() 方法,用于停止 actor 并执行任何必要的清理

    ¥A .stop() method which stops the actor and performs any necessary cleanup

  • 针对 可观察的 (opens new window) 角色的 .subscribe(...) 方法。

    ¥A .subscribe(...) method for actors that are Observable (opens new window).

所有现有的调用服务模式都适合此接口:

¥All the existing invoked service patterns fit this interface:

  • 调用的 Promise 是忽略任何接收到的事件并最多将一个事件发送回父级的参与者

    ¥Invoked promises are actors that ignore any received events and send at most one event back to the parent

  • 调用的回调 是可以向父级发送事件(第一个 callback 参数)、接收事件(第二个 onReceive 参数)并对其进行操作的参与者

    ¥Invoked callbacks are actors that can send events to the parent (first callback argument), receive events (second onReceive argument), and act on them

  • 调用机器 是参与者,可以将事件发送到父级(sendParent(...) 操作)或它引用的其他参与者(send(...) 操作)、接收事件、对其进行操作(状态转换和操作)、生成新参与者(spawn(...) 函数)和停止参与者 。

    ¥Invoked machines are actors that can send events to the parent (sendParent(...) action) or other actors it has references to (send(...) action), receive events, act on them (state transitions and actions), spawn new actors (spawn(...) function), and stop actors.

  • 调用的可观察量 是参与者,其发出的值表示要发送回父级的事件。

    ¥Invoked observables are actors whose emitted values represent events to be sent back to the parent.

什么是触发值?

Actor 发出的值是订阅者在 Actor 的 .subscribe(...) 方法中接收的值。

¥An actor's emitted value is the value that subscribers receive in the actor's .subscribe(...) method.

  • 对于服务,会发出当前状态。

    ¥For services, the current state is emitted.

  • 对于 promise,会发出已解决的值(如果未实现则为 undefined)。

    ¥For promises, the resolved value (or undefined if unfulfilled) is emitted.

  • 对于可观察量,会发出最新的发出值。

    ¥For observables, the latest emitted value is emitted.

  • 对于回调,不会发出任何内容。

    ¥For callbacks, nothing is emitted.

# 生成角色

¥Spawning Actors

正如在基于 Actor 模型的语言(如 阿卡 (opens new window)Erlang (opens new window))中一样,Actor 在 context 中生成并被引用(作为 assign(...) 操作的结果)。

¥Just as in Actor-model-based languages like Akka (opens new window) or Erlang (opens new window), actors are spawned and referenced in context (as the result of an assign(...) action).

  1. 'xstate' 导入 spawn 函数

    ¥Import the spawn function from 'xstate'

  2. assign(...) 操作中,使用 spawn(...) 创建新的 actor 引用

    ¥In an assign(...) action, create a new actor reference with spawn(...)

spawn(...) 函数通过提供 1 或 2 个参数来创建参与者引用:

¥The spawn(...) function creates an actor reference by providing 1 or 2 arguments:

  • entity - 代表参与者行为的(反应性)值或机器。entity 的可能类型:

    ¥entity - the (reactive) value or machine that represents the behavior of the actor. Possible types for entity:

  • name(可选) - 唯一标识演员的字符串。对于所有生成的参与者和调用的服务来说,这应该是唯一的。

    ¥name (optional) - a string uniquely identifying the actor. This should be unique for all spawned actors and invoked services.

或者 spawn 接受选项对象作为第二个参数,其中可能包含以下选项:

¥Alternatively spawn accepts an options object as the second argument which may contain the following options:

  • name(可选) - 唯一标识演员的字符串。对于所有生成的参与者和调用的服务来说,这应该是唯一的。

    ¥name (optional) - a string uniquely identifying the actor. This should be unique for all spawned actors and invoked services.

  • autoForward - (可选)true 如果发送到此计算机的所有事件也应发送(或转发)到调用的子级(默认情况下为 false

    ¥autoForward - (optional) true if all events sent to this machine should also be sent (or forwarded) to the invoked child (false by default)

  • sync - (可选)true 如果该机器应自动订阅生成的子机器的状态,则可以从子机器引用上的 .getSnapshot() 检索该状态

    ¥sync - (optional) true if this machine should be automatically subscribed to the spawned child machine's state, the state can be retrieved from .getSnapshot() on the child machine ref













 
 








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,
            // add a new todoMachine actor with a unique name
            ref: spawn(todoMachine, `todo-${event.id}`)
          }
        ]
      })
    }
    // ...
  }
});

如果你不向 spawn(...) 提供 name 参数,则会自动生成唯一名称。该名称将是不确定的:警告:。命名参与者更容易在其他 API 调用中引用,请参阅 向 Actor 发送事件

¥If you do not provide a name argument to spawn(...), a unique name will be automatically generated. This name will be nondeterministic ⚠️. A named actor is easier to be referenced in other API calls, see Sending Events to Actors.

提示

const actorRef = spawn(someMachine) 视为 context 中的正常值。你可以根据你的逻辑要求将此 actorRef 放置在 context 内的任何位置。只要它位于 assign(...) 中的赋值函数内,它的作用域就会限定在它生成的服务范围内。

¥Treat const actorRef = spawn(someMachine) as just a normal value in context. You can place this actorRef anywhere within context, based on your logic requirements. As long as it's within an assignment function in assign(...), it will be scoped to the service from where it was spawned.

警告

不要在赋值函数之外调用 spawn(...)。这将产生一个孤儿角色(没有父级),这不会产生任何副作用。

¥Do not call spawn(...) outside of an assignment function. This will produce an orphaned actor (without a parent) which will have no effect.

// ❌ Never call spawn(...) externally
const someActorRef = spawn(someMachine);

// ❌ spawn(...) is not an action creator
{
  actions: spawn(someMachine);
}

// ❌ Do not assign spawn(...) outside of an assignment function
{
  actions: assign({
    // remember: this is called immediately, before a service starts
    someActorRef: spawn(someMachine)
  });
}

// ✅ Assign spawn(...) inside an assignment function
{
  actions: assign({
    someActorRef: () => spawn(someMachine)
  });
}

不同类型的值可以作为参与者产生。

¥Different types of values can be spawned as actors.

# 向 Actor 发送事件

¥Sending Events to Actors

使用 send() 行动,事件可以通过 目标表达 发送给 actor:

¥With the send() action, events can be sent to actors via a target expression:













 





const machine = createMachine({
  // ...
  states: {
    active: {
      entry: assign({
        someRef: () => spawn(someMachine)
      }),
      on: {
        SOME_EVENT: {
          // Use a target expression to send an event
          // to the actor reference
          actions: send({ type: 'PING' }, { to: (context) => context.someRef })
        }
      }
    }
  }
});

提示

如果你为 spawn(...) 提供唯一的 name 参数,则可以在目标表达式中引用它:

¥If you provide an unique name argument to spawn(...), you can reference it in the target expression:

const loginMachine = createMachine({
  // ...
  entry: assign({
    formRef: () => spawn(formMachine, 'form')
  }),
  states: {
    idle: {
      on: {
        LOGIN: {
          actions: send({ type: 'SUBMIT' }, { to: 'form' })
        }
      }
    }
  }
});

# 阻止角色

¥Stopping Actors

角色停止使用 stop(...) 动作创建器:

¥Actors are stopped using the stop(...) action creator:

const someMachine = createMachine({
  // ...
  entry: [
    // Stopping an actor by reference
    stop((context) => context.someActorRef),
    // Stopping an actor by ID
    stop('some-actor')
  ]
});

# 衍生 Promise

¥Spawning Promises

就像 调用 promise 一样,promise 可以作为 actor 生成。发送回机器的事件将是 'xstate.done.actor.<ID>' 操作,promise 响应作为有效负载中的 data 属性:

¥Just like invoking promises, promises can be spawned as actors. The event sent back to the machine will be a 'xstate.done.actor.<ID>' action with the promise response as the data property in the payload:











 


// Returns a promise
const fetchData = (query) => {
  return fetch(`http://example.com?query=${query}`).then((data) => data.json());
};

// ...
{
  actions: assign({
    ref: (_, event) => spawn(fetchData(event.query))
  });
}
// ...

警告

不建议生成 Promise Actor,因为 调用 promise 是更好的模式,因为它们依赖于状态(状态更改时自动取消)并且具有更可预测的行为。

¥It is not recommended to spawn promise actors, as invoking promises is a better pattern for this, since they are dependent on state (self-cancelling upon state change) and have more predictable behavior.

# 生成回调

¥Spawning Callbacks

就像 调用回调 一样,回调可以作为 actor 生成。此示例模拟了一个计数器间隔参与者,该参与者每秒递增自己的计数,但也可以对 { type: 'INC' } 事件做出反应。

¥Just like invoking callbacks, callbacks can be spawned as actors. This example models a counter-interval actor that increments its own count every second, but can also react to { type: 'INC' } events.






















 





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)
    })
  }
  // ...
});

然后可以将事件发送给参与者:

¥Events can then be sent to the actor:





 
 
 



const machine = createMachine({
  // ...
  on: {
    'COUNTER.INC': {
      actions: send({ type: 'INC' }, { to: (context) => context.counterRef })
    }
  }
  // ...
});

# 衍生可观察物

¥Spawning Observables

就像 调用可观察值 一样,observables 可以作为 actor 生成:

¥Just like invoking observables, observables can be spawned as actors:





















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': { /* ... */ }
  }
});

# 衍生机器

¥Spawning Machines

机器是使用参与者的最有效方式,因为它们提供了最多的功能。生成机就像 调用机器 一样,其中 machine 被传递到 spawn(machine) 中:

¥Machines are the most effective way to use actors, since they offer the most capabilities. Spawning machines is just like invoking machines, where a machine is passed into spawn(machine):













 












 



 
 
 


















const remoteMachine = createMachine({
  id: 'remote',
  initial: 'offline',
  states: {
    offline: {
      on: {
        WAKE: 'online'
      }
    },
    online: {
      after: {
        1000: {
          actions: sendParent({ type: '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'

# 同步和阅读状态 4.6.1+

¥Syncing and Reading State 4.6.1+

Actor 模型的主要原则之一是 Actor 状态是私有的且本地的 - 除非参与者选择通过消息传递来共享它,否则它永远不会被共享。坚持这种模型,只要状态发生变化,Actor 就可以通过向其发送带有最新状态的特殊 "update" 事件来通知其父级。换句话说,父 Actor 可以订阅其子 Actor 的状态。

¥One of the main tenets of the Actor model is that actor state is private and local - it is never shared unless the actor chooses to share it, via message passing. Sticking with this model, an actor can notify its parent whenever its state changes by sending it a special "update" event with its latest state. In other words, parent actors can subscribe to their child actors' states.

为此,请将 { sync: true } 设置为 spawn(...) 的选项:

¥To do this, set { sync: true } as an option to spawn(...):




 





// ...
{
  actions: assign({
    // Actor will send update event to parent whenever its state changes
    someRef: () => spawn(todoMachine, { sync: true })
  });
}
// ...

这将自动为机器订阅生成的子机器的状态,该状态保持更新并且可以通过 getSnapshot() 访问:

¥This will automatically subscribe the machine to the spawned child machine's state, which is kept updated and can be accessed via getSnapshot():

someService.onTransition((state) => {
  const { someRef } = state.context;

  console.log(someRef.getSnapshot());
  // => State {
  //   value: ...,
  //   context: ...
  // }
});

警告

默认情况下,sync 设置为 false。当 sync 禁用时,切勿从 .getSnapshot() 读取 actor 的状态;否则,你最终将引用过时的状态。

¥By default, sync is set to false. Never read an actor's state from .getSnapshot() when sync is disabled; otherwise, you will end up referencing stale state.

# 发送更新 4.7+

¥Sending Updates 4.7+

对于不与父机器同步的 actor,actor 可以通过 sendUpdate() 向其父机器发送显式事件:

¥For actors that are not synchronized with the parent, the actor can send an explicit event to its parent machine via sendUpdate():

import { createMachine, sendUpdate } from 'xstate';

const childMachine = createMachine({
  // ...
  on: {
    SOME_EVENT: {
      actions: [
        // ...
        // Creates an action that sends an update event to parent
        sendUpdate()
      ]
    }
  }
});

提示

更喜欢显式向父级发送事件 (sendUpdate()),而不是订阅每个状态更改。与生成的计算机同步可能会导致 "chatty" 事件日志,因为子级的每次更新都会导致从子级发送到父级的新 "xstate.update" 事件。

¥Prefer sending events to the parent explicitly (sendUpdate()) rather than subscribing to every state change. Syncing with spawned machines can result in "chatty" event logs, since every update from the child results in a new "xstate.update" event sent from the child to the parent.

# 快速参考

¥Quick Reference

导入 spawn 来生成 actor:

¥Import spawn to spawn actors:

import { spawn } from 'xstate';

assign 动作创作者中的衍生演员:

¥Spawn actors in assign action creators:

// ...
{
  actions: assign({
    someRef: (context, event) => spawn(someMachine)
  });
}
// ...

产生不同类型的演员:

¥Spawn different types of actors:

// ...
{
  actions: assign({
    // From a promise
    promiseRef: (context, event) =>
      spawn(
        new Promise((resolve, reject) => {
          // ...
        }),
        'my-promise'
      ),

    // From a callback
    callbackRef: (context, event) =>
      spawn((callback, receive) => {
        // send to parent
        callback('SOME_EVENT');

        // receive from parent
        receive((event) => {
          // handle event
        });

        // disposal
        return () => {
          /* do cleanup here */
        };
      }),

    // From an observable
    observableRef: (context, event) => spawn(someEvent$),

    // From a machine
    machineRef: (context, event) =>
      spawn(
        createMachine({
          // ...
        })
      )
  });
}
// ...

与 Actor 同步状态:

¥Sync state with an actor:

// ...
{
  actions: assign({
    someRef: () => spawn(someMachine, { sync: true })
  });
}
// ...

从演员那里获取快照:4.20.0+

¥Getting a snapshot from an actor: 4.20.0+

service.onTransition((state) => {
  const { someRef } = state.context;

  someRef.getSnapshot();
  // => State { ... }
});

使用 send 动作创建器将事件发送给演员:

¥Send event to actor with send action creator:

// ...
{
  actions: send(
    { type: 'SOME_EVENT' },
    {
      to: (context) => context.someRef
    }
  );
}
// ...

使用 send 表达式将带有数据的事件发送给 actor:

¥Send event with data to actor using a send expression:

// ...
{
  actions: send((context, event) => ({ ...event, type: 'SOME_EVENT' }), {
    to: (context) => context.someRef
  });
}
// ...

使用 sendParent 动作创建者将事件从演员发送到父级:

¥Send event from actor to parent with sendParent action creator:

// ...
{
  actions: sendParent({ type: 'ANOTHER_EVENT' });
}
// ...

使用 sendParent 表达式将带有数据的事件从参与者发送到父级:

¥Send event with data from actor to parent using a sendParent expression:

// ...
{
  actions: sendParent((context, event) => ({
    ...context,
    type: 'ANOTHER_EVENT'
  }));
}
// ...

context 的参考演员:

¥Reference actors from context:

someService.onTransition((state) => {
  const { someRef } = state.context;

  console.log(someRef);
  // => { id: ..., send: ... }
});