当前位置:首页 > 文章列表 > 文章 > 前端 > XState父状态机怎样向子actor发送事件

XState父状态机怎样向子actor发送事件

来源:17golang原创 2026-10-13 20:59:11 0浏览 收藏

在 XState 里,父状态机不能直接修改子 actor 的内部状态;正确做法是先让子 actor 处于运行状态,再用 sendTo 把事件发到它的 actor ID 或 actor ref。最容易出错的地方不是事件对象本身,而是把 machine 配置误当成 actor、ID 写错,或在 invoke 对应的状态已经退出后继续发送。

官方文档:https://stately.ai/docs/

要点速览
  • 固定 invoke 子 actor 时,父机用 sendTo('子actor的id', { type: '事件名' })。
  • 动态 spawn 的子 actor 优先使用 children 或 context 中保存的 actor ref。
  • 排查时依次确认目标存在、事件类型匹配、父状态没有停止或重建子 actor。

先确认父机手里拿的是正在运行的 actor

createMachine 返回的是 actor logic,createActor 返回的才是可以接收事件的 actor。父子通信发生在运行时:父机进入声明 invoke 的状态后,子 actor 才会按它的生命周期启动;父机离开该状态时,被 invoke 的子 actor 也会停止。

XState 父actor、invoke子actor、actor ID 与事件对象之间的静态关系说明图
图1:父 actor 通过稳定 ID 找到 invoke 子 actor,再发送事件的结构说明图,不是运行截图。

因此,下面的排查顺序很实用:先看父机当前是否仍在 invoke 状态,再看 snapshot.children 中是否有对应 ID,最后才检查事件名和负载。只要子 actor 已因状态退出而停止,继续发送就不会产生你期待的状态变化。

固定子 actor 用 sendTo 按 ID 发送

下面是一个最小的 XState v5 写法。父机收到外部的 START_WORK 后,把 RUN 事件发给 ID 为 worker 的被 invoke 子 actor。示例刻意把发送动作放在外部事件之后,避免把“父状态刚进入时子 actor 是否已经启动”混在第一个问题里。

import { createActor, createMachine, sendTo, setup } from 'xstate';

const workerMachine = createMachine({
  id: 'worker-machine',
  initial: 'idle',
  states: {
    idle: {
      on: {
        RUN: 'working', // 子 actor 只在 idle 状态接收 RUN
      },
    },
    working: {
      on: {
        STOP: 'idle', // 用显式事件回到可再次运行的状态
      },
    },
  },
});

const parentMachine = setup({
  actors: {
    worker: workerMachine,
  },
}).createMachine({
  id: 'parent-machine',
  initial: 'ready',
  states: {
    ready: {
      invoke: {
        id: 'worker', // sendTo 的目标 ID 必须与这里完全一致
        src: 'worker',
      },
      on: {
        START_WORK: {
          actions: sendTo('worker', { type: 'RUN' }), // 父 actor 向子 actor 发事件
        },
      },
    },
  },
});

const parentActor = createActor(parentMachine);
parentActor.start();
parentActor.send({ type: 'START_WORK' }); // 发送给父 actor,再由父机转发给 worker

这里有三个容易混淆的名字:src: 'worker' 是注册的 actor logic 名称,id: 'worker' 是运行中子 actor 的识别名,RUN 才是子机声明过的事件类型。父机发送的是事件对象,不是字符串;这也是 XState v5 与旧写法之间常见的迁移差异。

动态 spawn 时使用 actor ref

如果子 actor 数量不固定,例如每个上传任务对应一个 actor,就不应把所有目标都写成固定字符串。spawn 返回 actor ref,父机可以把它保存在上下文或直接通过 children 找到,再将事件发给这个具体引用。

import { createMachine } from 'xstate';

const uploadMachine = createMachine({
  id: 'upload',
  initial: 'queued',
  states: {
    queued: {
      on: {
        CANCEL: 'cancelled', // 动态子 actor 仍然通过自己的事件表改变状态
      },
    },
    cancelled: {},
  },
});

const uploadParent = createMachine({
  id: 'upload-parent',
  initial: 'active',
  states: {
    active: {
      entry: ({ spawn }) => {
        spawn(uploadMachine, { id: 'upload-42' }); // 实际项目可把 ref 放入 context
      },
      on: {
        CANCEL_UPLOAD: ({ children }, { sendTo }) => {
          const upload = children['upload-42'];
          if (upload) {
            sendTo(upload, { type: 'CANCEL' }); // 目标存在才发送,避免悬空引用
          }
        },
      },
    },
  },
});

动态场景的核心不是“把 ID 拼得更复杂”,而是持有 actor ref。实际代码还应在停止子 actor 的同一处移除 context 中保存的 ref,否则父机可能继续握着一个已经停止的对象。对于多个上传任务,可以用任务 ID 建立自己的映射,但映射里的 ref 必须和当前存活的子 actor 同步。

XState 固定 ID 与动态 actor ref 的发送目标、事件契约和生命周期检查关系图
图2:固定 ID、动态 actor ref 与事件契约之间的排查结构图,不是调试器截图。

事件没有到达时按三层检查

第一层是目标。先确认 sendTo 指向的 ID 正是 invoke 配置中的 id,或者 actor ref 仍存在。不要把 src 名称和 id 当成同一个概念,也不要在父机已经离开 invoke 状态后继续用旧目标。

第二层是事件契约。子机必须在当前状态的 on 中声明相同的 type。例如子机只处理 RUN,父机发送 START,事件可以到达但不会触发目标转移。带数据时再检查负载字段是否符合 TypeScript 类型。

第三层是生命周期。invoke 子 actor 随声明它的状态进入和退出;spawn 子 actor 则由父 actor 持有,通常要显式停止。父机重进状态时,旧 invoke 子 actor 可能已经被停止并换成新的实例,保存在 context 里的旧 ref 不应继续使用。

现象优先检查修复方向
动作执行但子机无变化事件 type 是否写成子机当前 on 的名称统一事件对象和负载类型
发送到固定字符串失败invoke 的 id 是否与 sendTo 目标一致稳定 ID,别误用 src 名称
动态任务偶尔收不到事件actor ref 是否已停止或被替换同步 children/context 映射

常见问题

父机能不能直接改子 actor 的 context?

不应该。actor 的内部状态由它自己根据收到的事件更新;父机应发送语义明确的事件,让子机决定如何改变 context。

固定 invoke 和动态 spawn 都能用 sendTo 吗?

可以,但固定 invoke 通常按稳定 ID 发送,动态 spawn 更适合使用 actor ref。目标选择应跟随生命周期模型,而不是只看代码短不短。

子 actor 怎样把结果通知父 actor?

可以通过父 actor ref 使用 sendTo 回传事件;旧项目也可能看到 sendParent,但显式传递 actor ref 更容易表达依赖并保持类型边界。

把父子通信拆成“目标、事件、生命周期”三层后,XState 的事件转发就不再是黑盒:父机负责找到正确的 actor,子机负责解释事件,状态机负责决定这个 actor 是否仍然存活。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Alpine.js $nextTick怎样等待DOM更新Alpine.js $nextTick怎样等待DOM更新
上一篇
Alpine.js $nextTick怎样等待DOM更新
XState guard与action怎样分工
下一篇
XState guard与action怎样分工
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    463次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    526次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    543次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    492次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    311次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码