XState:4.7 版及未来 XState 的未来

2025-06-04

XState:4.7 版及未来

XState 的未来

XState 4.7 版本刚刚发布。这是一个小的版本升级,但对内部算法进行了重大改进,增加了许多新功能,修复了一些错误,并提升了 TypeScript 体验。它还为更多实用程序(例如@xstate/test@xstate/react)的开发铺平了道路,并与整个生态系统乃至跨语言的其他第三方工具兼容。

什么是 XState?

XState 是一个 JavaScript(和 TypeScript)库,用于创建并解释状态机和状态图。状态机在逻辑结构上强制执行一组特定的“规则”,例如:

  • 状态的数量是有限的(例如"loading"或),这与上下文(具有潜在无限可能性的相关数据,例如"success"不同emailage
  • 事件的数量是有限的(比如{ type: 'FETCH', query: "..." }可以触发状态之间转换的事件)。
  • 每个状态都有转换,即“给定某个事件,转到下一个状态和/或执行这些操作”。

您不需要状态机库来执行此操作,因为您可以使用switch语句来代替:

switch (state.status) {
  case 'idle': // finite state
    switch (event.type) {
      case 'FETCH':
        return {
          ...state,
          status: 'loading',
          query: event.query
        };
      // ...
    // ...
  // ...
}
Enter fullscreen mode Exit fullscreen mode

但说实话,这样写可以说更干净一些:

const machine = Machine({
  initial: 'idle',
  states: {
    idle: {
      on: {
        FETCH: {
          target: 'loading',
          actions: assign({ query: (_, event) => event.query })
        }
      }
    },
    // ...
  }
});
Enter fullscreen mode Exit fullscreen mode

而且,它还可以直接将这段机器代码复制粘贴到可视化工具(如XState Viz )中并将其可视化,就像在“不,禁用按钮不是应用程序逻辑”文章末尾所做的那样

XState Viz 上的状态机可视化
在 XState Viz 上查看此可视化

然后是状态图,它是 David Harel 于 1989 年创建的有限状态机的扩展(阅读论文📑)。状态图提供了许多改进,并缓解了使用扁平有限状态机的许多问题,例如:

  • 嵌套状态(层次结构)
  • 平行状态(正交性)
  • 历史状态
  • 进入、退出和转换操作
  • 瞬态
  • 活动(正在进行的行动)
  • 与多台机器通信(调用服务)
  • 延迟转换
  • 以及更多

这些功能你肯定不想自己实现,所以才有了 XState 这样的库。接下来我们来看看……

XState 4.7 有哪些新功能?

这个小版本经过数月的精心打造,得到了Mateusz Burzyński(又名 AndaristRake) 👏 的大力帮助。之所以耗时如此之久,是因为我们正在内部重新设计算法,使其更简洁、更符合SCXML 规范,并与生态系统中越来越多的工具兼容。此次重构也使添加新功能变得更加容易,并有望鼓励更多贡献者参与该项目。此外,它还消除了一些边缘情况的错误,这些错误虽然有解决方法,但在之前的版本中可能会导致开发者体验不佳。

重构内部算法

创建状态图库有多难?远比想象的要难得多,尤其是如果你想要遵循冗长却完善的SCXML 规范。甚至还有一些库可以将 SCXML 代码直接与 JavaScript 集成,例如 Jacob Beard 出色的SCION 工具,我强烈推荐你去看看。它对 XState 的开发产生了巨大的启发,并且 XState 的测试针对的也是许多相同的代码。

SCXML 指定了一种用于 SCXML 解释的算法,该算法以伪代码编写,但可直接迁移到多种流行语言。重构过程中更严格地遵循了该算法,从而简化了大量代码库,并消除了对诸如 之类的临时数据结构的需求StateTree,该数据结构过去用于跟踪给定转换中哪些状态节点处于“活动”状态(现在只是一个集合)。

因此,核心代码库变得更小,算法也更快(确定下一个状态基本上是 O(1) 的查找,最坏情况是 O(n)),代码库也更易于使用和贡献。在迈向 5.0 的过程中,我们将继续改进所使用的算法。

类型状态

类型状态对开发者来说真的很有用。它们在 Rust 中很流行,这篇关于Rust 中的类型状态模式的文章对它们进行了优雅的描述:

类型状态是一种将状态属性(程序正在处理的动态信息)移动到类型级别(编译器可以提前检查的静态世界)的技术。

无需学习 Rust 或深入研究维基百科,我们来举一个经典的例子:加载数据。你可以这样表示状态的上下文:

interface User {
  name: string;
}

interface UserContext {
  user?: User;
  error?: string;
}
Enter fullscreen mode Exit fullscreen mode

这种类型安全的声明允许您有效地进行防御性编程,但当您 100% 确定user已定义时,它可能会有点烦人:

if (state.matches('success')) {
  if (!state.context.user) {
    // this should be impossible!
    // the user definitely exists!
    throw new Error('Something weird happened');
  }

  return state.context.user.name;
}
Enter fullscreen mode Exit fullscreen mode

在 4.7 中,XState 允许您使用 Typestates 来表示您的状态,这样您就可以告诉编译器您知道context在任何给定状态下应该如何:

GIF 显示可选用户对象将在成功状态下定义

这非常有用,可以提升开发者体验,但应该作为强有力的指导,而非保证。它通过在 TypeScript 中使用可区分联合来定义状态,但其实现方式需要 TypeScript 3.7 及更高版本。还有一些问题需要解决,因为我们基本上是在尝试欺骗 TypeScript 了解一些关于状态机的额外信息,而这些信息在静态类型语言中很难/不可能推断出来。(也许有一天 JavaScript 会拥有依赖类型语言的特性。)

更好的服务体验

XState 使调用外部“服务”成为一等公民。如果您对这个概念还不熟悉,那么现在只需理解它回答了“多个状态机如何相互通信?”这个问题,答案是使用事件作为主要的通信机制。在 4.7 中,开发人员在这方面的体验得到了改进:

  • 现在可以通过对象 ID 引用已调用的服务state.children。因此,如果某个状态使用 调用某个服务id: 'fetchUser',则该调用将出现在 上state.children.fetchUser
  • 新的forwardTo()动作创建器允许您将事件转发给调用的服务,从而减少了很多样板:
on: {
  SOME_EVENT: {
    actions: forwardTo('someService')
  }
}
Enter fullscreen mode Exit fullscreen mode
  • SCXML 中有一个 的概念sessionid,它是每个调用服务的唯一标识符。XState 4.7 通过在 中保留对此 的引用state._sessionid(该引用对应于 SCXML_sessionid变量)使其与 SCXML 更加兼容。
  • XState 可以使用它_sessionid来确定哪个服务发送了事件,因此它可以使用新的respond()动作创建器返回一个事件作为响应:
const authServerMachine = Machine({
  initial: 'waitingForCode',
  states: {
    waitingForCode: {
      on: {
        CODE: {
          actions: respond('TOKEN', { delay: 10 })
        }
      }
    }
  }
});

const authClientMachine = Machine({
  initial: 'idle',
  states: {
    idle: {
      on: { AUTH: 'authorizing' }
    },
    authorizing: {
      invoke: {
        id: 'auth-server',
        src: authServerMachine
      },
      entry: send('CODE', { to: 'auth-server' }),
      on: {
        TOKEN: 'authorized'
      }
    },
    authorized: {
      type: 'final'
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

您也可以创建自己的自定义操作创建器,并实现您可能已经熟悉的模式(如果您曾经使用过微服务)。

通配符描述符

如果您曾经想过,当收到任何(未指定的)事件时,状态会立即转换?那么您很幸运,因为 XState 现在支持通配符描述符,这是一种事件描述符(SCXML),用于描述给定状态下任何事件的转换:

const quietMachine = Machine({
  id: 'quiet',
  initial: 'idle',
  states: {
    idle: {
      on: {
        WHISPER: undefined,
        // On any event besides a WHISPER, transition to the 'disturbed' state
        '*': 'disturbed'
      }
    },
    disturbed: {}
  }
});

quietMachine.transition(quietMachine.initialState, 'WHISPER');
// => State { value: 'idle' }

quietMachine.transition(quietMachine.initialState, 'SOME_EVENT');
// => State { value: 'disturbed' }
Enter fullscreen mode Exit fullscreen mode

更多

请参阅https://github.com/davidkpiano/xstate/releases/tag/v4.7.0了解此小版本的最新更新概述。

XState 的未来

所有这些都引出了一个大问题:XState 的未来计划/目标是什么?首先要意识到的是,XState 不仅仅是一个状态管理库,而且状态管理也从来不是它的唯一目标。XState 致力于为 JavaScript 生态系统带来两样东西:

  • 状态机/状态图,用于对任何单个组件的逻辑进行建模
  • 参与者模型,用于模拟组件如何相互通信以及如何在系统中表现

这些都是非常古老、非常有用且久经考验的概念。它们带来的好处不容低估,并凸显了 XState 及相关工具的未来规划:

  • 更好的可视化工具,包括更新的可视化工具、Firefox 和 Chrome 的开发工具(正在进行中!)、VS Code 的开发工具,以及与其他图形可视化工具(如PlantUMLGraphViz)的集成
  • 完全兼容SCXML,这将允许用 XState 编写的状态图在具有 SCXML 工具的其他语言中重复使用,因为它是一个真正与语言无关的规范
  • 示例目录,用于展示许多用例的常见模式和最佳实践
  • 分析、测试和模拟工具

以及对 XState 5.0 版本的一些初步想法:

  • 更好的类型安全性和更无缝的 TypeScript 体验
  • 用于编译时提示/警告和运行时优化的静态分析工具
  • 一种更“实用”且完全可选的语法,用于更自然地定义状态和转换(开发人员体验)
  • 更高级别的状态类型,例如"task"和,"choice"可以更轻松地定义工作流程并删除一些样板

我们也在听取您在XState 愿望清单主题中向我们提出的想法,因此请发布您希望看到的内容!

更多信息

如果您对 XState 或状态图感兴趣,那么有很多精彩的资源,包括:

文章来源:https://dev.to/davidkpiano/xstate-version-4-7-and-the-future-2ehk
PREV
.NET MAUI 中的所有列表 .NET MAUI 中的所有列表
NEXT
20+ 使用 React.js 构建的精彩网站