Skip to content

Invoked actor

基本概念

invoke actor 是状态机在某个状态下“托管的一个外部执行单元”用来做 异步、并发、可取消、可通信 的事情。

状态机可以在某个状态中“调用”一个或多个 actor。当状态进入时,被调用的 actor 将开始运行,并在状态退出时停止。任何 XState actor 都可以被调用,包括简单的基于 Promise 的 actor,甚至是复杂的基于状态机的 actor。

ts
    loading: {
      invoke: {
        id: 'getUser',
        src: 'fetchUser',
        input: ({ context: { userId } }) => ({ userId }),
        onDone: {
          target: 'success',
          actions: assign({ user: ({ event }) => event.output }),
        },
        onError: {
          target: 'failure',
          actions: assign({ error: ({ event }) => event.error }),
        },
      },
    },

当状态机进入loading状态时:

  • 创建并启动一个 actor 实例fetchUser
  • 当离开这个 state 时,自动停止 / 取消 这个 actor
  • 如果在fetchUser执行期间,进行了状态的切换则:
    • 不会触发 onDone
    • 不会触发 onError
    • 不会更新 context
    • 不会再产生任何状态变化
ts
import { setup, createActor, fromPromise, assign } from 'xstate';

const fetchUser = (userId: string) =>
  fetch(`https://example.com/${userId}`).then((response) => response.text());

const userMachine = setup({
  types: {
    context: {} as {
      userId: string;
      user: object | undefined;
      error: unknown;
    },
  },
  actors: {
    fetchUser: fromPromise(async ({ input }: { input: { userId: string } }) => {
      const user = await fetchUser(input.userId);

      return user;
    }),
  },
}).createMachine({
  id: 'user',
  initial: 'idle',
  context: {
    userId: '42',
    user: undefined,
    error: undefined,
  },
  states: {
    idle: {
      on: {
        FETCH: { target: 'loading' },
      },
    },
    loading: {
      invoke: {
        id: 'getUser',
        src: 'fetchUser',
        input: ({ context: { userId } }) => ({ userId }),
        onDone: {
          target: 'success',
          actions: assign({ user: ({ event }) => event.output }),
        },
        onError: {
          target: 'failure',
          actions: assign({ error: ({ event }) => event.error }),
        },
      },
    },
    success: {},
    failure: {
      on: {
        RETRY: { target: 'loading' },
      },
    },
  },
});

Invoked actor和Actions有什么不同

Actions 是“触发就不管了”

  • 同步执行

  • 不会被 await

  • 不影响状态迁移

  • 不能直接参与错误处理

ts
actions: async () => {
  await fetch('/api'); // ❌ 这个 await 对状态机没意义
}

states transition synchronously

  • 状态迁移是原子、同步

  • 不存在“迁移中”这种中间态

  • 你永远可以假设: 发送事件 → 立刻进入新状态

ts
invoke: {
  src: 'fetchUser',
  onError: {
    target: 'failure',
    actions: assign({ error: ({ event }) => event.error })
  }
}

一个调用在状态节点的配置中通过 invoke 属性定义,其值是一个包含以下内容的对象:

src

在创建 actor 时要调用的 actor 逻辑 的来源,或指向机器中 提供实现 中定义的 actor 逻辑的字符串。

  • Object
ts
invoke: {
  src: someMachine,
}
  • String
ts
invoke: {
  src: 'fetchUser',
}
ts
createMachine(
  {},
  {
    actors: {
      fetchUser: fetchUserMachine,
    },
  }
);
ts
invoke: {
  id: 'fetchUser',
  src: fetchUserMachine,
}

Invoked Actor Object

src

ts
src:ActorLogic|string
  • string:actors中声明的引用名
ts
createMachine({}, {
  actors: {
    fetchUser: fetchUserMachine,
  },
});

id

用于标识创建的invoked actor实例的名称,在其父机器中是唯一的。

XState 为「某个 actor 完成」自动生成的事件类型名就会用到id:

ts
xstate.done.actor. + invoke 的 id
  • 这个事件通常是由父机器自动调用的,我们无需关心其内部具体的名字。

  • 如果我们没有写id,xstate会为我们自动生成不可预测的id

input

传递给Actor的输入

onDone

Actor完成时触发

ts
onDone(({context,event})=>{...})
  • context:外部状态机的context
  • event:父机器发送的事件

fetchUser 这个 actor 完成时,XState 自动发送 一个事件给父状态机:

{
  type: 'xstate.done.actor.getUser',
  output: <Promise resolve 的值>
}

onError

Actor抛出错误时调用

onSnapshot

快照发生变化时触发

systemId

一个字符串,用于标识Actor,系统范围内唯一。

复用Invoked Actor

Invoked Actor Object

Array<Invoked Actor Object>

ts
const vitalsWorkflow = createMachine({
  states: {
    CheckVitals: {
      invoke: [
        { src: 'checkTirePressure' },
        { src: 'checkOilPressure' },
        { src: 'checkCoolantLevel' },
        { src: 'checkBattery' },
      ],
    },
  },
});

Invoked Actor

onDone

Actor完成时触发。Actor执行完成,会有一个output。可以通过event.output获取

ts
      invoke: {
        src: 'fetchForm',
        onDone: [
          {
            target: 'step1',
            guard: ({ event }) => (event.output as FormDTO).step === 'step1',
            actions: 'restoreForm',
          },
          {
            target: 'step2',
            guard: ({ event }) => (event.output as FormDTO).step === 'step2',
            actions: 'restoreForm',
          },
          {
            target: 'review',
            guard: ({ event }) => (event.output as FormDTO).step === 'review',
            actions: 'restoreForm',
          },
          {
            target: 'step1',
            actions: 'restoreForm',
          },
        ],
        onError: {
          // 如果加载失败,从第一步开始
          target: 'step1',
        },
      },