我给 XState 和 statecharts 的情书♥
在 Twitter 上关注我@tim_deschryver | 订阅新闻通讯| 最初发布于timdeschryver.dev。
XState是由David K. 🎹创建的 JavaScript 库。
XState 是一个用于创建、解释和执行状态图的库。状态图是一种用于建模有状态、响应式系统的形式化方法。计算机科学家 David Harel 在其 1987 年的论文《状态图:复杂系统的可视化形式化方法》中提出了这种形式化方法,作为状态机的扩展。
我的 XState 之旅始于一年前,当时我观看了 David Khourshid 的演讲《用有限自动机实现无限更好的 UI》。他的演讲让我茅塞顿开。真正让我震惊的是,我之前的设计(UI 状态)方式很糟糕,因为缺乏经验,导致它变得复杂。他重点提到的例子是一个简单的 AJAX 请求,为了处理所有可能的情况,代码很快就会变得杂乱无章。David 称之为自下而上(🍑🆙)的方法。
XState 吸引我的地方在于它给人一种熟悉的感觉,而且作为一个很棒的附加功能,我从多个演示中看到的代码都表明它可读性强且易于理解。
起初,我把它看作一个声明式的 Redux Store,一个门口有守卫阻止恶意类型进入的 Store,而不是一个大门始终敞开的 Store。
唯一可见的区别是它没有 Reducer。而 XState(或者说一般的状态机)则通过状态转换来修改状态。思路略有不同,但结果是一样的,都是一个新的状态。现在,经过一些概念验证之后,我想分享一下我喜欢 XState 的原因。
我为什么喜欢 XState
- 更难引入“无效”状态
- 思考状态和状态转换是构建(部分)应用程序的第一步,它迫使你在编写代码之前思考逻辑
- TypeScript,创建 XState 机是完全类型安全的
- 可以将状态机导出到状态可视化工具,您可以在一个清晰的图像中看到所有状态转换
- 状态机和状态图并不是什么新鲜事物,在我出生之前,这个概念就已经经过了实践检验
- 就像 redux 一样,状态是确定性的
- 文档写得很好,而且易于搜索,因此很容易找到所需的内容
- 逻辑可以被非开发人员理解和讨论
对最初想法的反思
虽然这些例子很容易理解,但编写我的第一台机器却很难。因为机器内部运作很容易理解,不需要太多代码,而且易于理解,所以创建机器的复杂性被隐藏了。对我来说,用这种声明式的方式编写代码是全新的体验,我花了一些时间才适应。一旦熟悉了语法,编写机器就变得容易多了,现在最耗时的部分就是绘制状态和转换的模型。
XState 和 redux 有一些相似之处,但并不完全相同。XState
不是通过一个全局存储来调度所有操作,而是通过多个较小的存储(Actor)来发送事件。这种架构称为Actor 模型。Actor 持有状态,可以接收消息并决定如何处理消息,向其他 Actor 发送消息,以及创建更多 Actor。
另一个区别是,机器非常明确,你不会意外陷入糟糕的状态。而使用 redux 则更容易陷入无效状态。
我相信之前使用过 redux 架构的经验对我入门很有帮助。
一个很棒的好处是,Machine 与 UI 框架/库无关,它不受特定 UI 框架的约束。同一台 Machine 可以在多个 UI 框架中复用,区别在于视图层以及用户与视图的交互方式。所有(业务)逻辑都只需在 Machine 中编写一次。它让你可以事后再考虑 UI,这是我之前没有想到的。这就是为什么我想创建一个实验,创建一个 Machine 并将其与 Angular、React、Svelte 和 Vue 一起使用。Table Machine 包含一个 Table 组件的逻辑。
演示项目:xstate-table
桌面机的状态可视化器如下所示:
图片左侧显示了状态逻辑。表格可以处于某种idle
状态,也可以处于某种dragging
状态。
当表格处于某种idle
状态时,会出现点击操作,其中一些操作带有保护子句。例如,ctrlClick
只有在满足条件时才会触发该操作isCtrlClick
。“常规”点击操作只有在其他点击保护子句均不满足时才会触发。
还有一个mousedown
动作,将状态从 转换为idle
。dragging
当表机器处于 状态时dragging
,mousemove
将管理选定的行,并使用新的坐标触发选择框的重新渲染。
在图片右侧,您可以看到桌子机器的选择状态。每个click
、mouseup
和mousemove
操作都会产生一个新状态:SINGLE_SELECTION
、MULTI_SELECTION
、 或EMPTY_SELECTION
。
在代码中我们可以使用这些状态来显示选择框或禁用按钮。
代码可以在GitHub上找到,或者在以下沙箱中找到:
关于演示项目的想法
这是一个简单的实验,但我对结果很满意。编写好机器后,即使我并非精通所有框架,也可以轻松地在不同的框架中实现它。文档中有一节介绍如何在框架内使用机器,这些示例提供了入门指导。对于 React,甚至有一个@xstate/react
带有useMachine
钩子的库,可以完成所有繁重的工作。基于此useMachine
,我创建了一个 Svelte 存储。对于 Angular (RxJS) 和 Vue,文档提供了一个示例。
在我的实验中,表格机器 100% 可复用,所以我认为这个实验很成功。我对每个框架所做的唯一改动是表格行的选择方式(用于判断行是否在选择框内)。默认实现已经document.querySelectorAll
完成了它的工作,但每个框架都有自己处理ref
HTML 元素的方式。我不知道为什么要使用框架的实现,我只是想知道是否可以这样做。
我对代码很满意,如果几个月后我再回来看它,我肯定会对它进行一些修改,但重要的是它要易于阅读。
结束语
XState 并非 Redux 的替代品,我仍然喜欢 Redux。它们各自服务于不同的需求。我认为 XState 是放置组件逻辑的理想位置。如果我必须创建相同的表组件,但不使用 XState,最终会得到一些难以理解的混乱代码。
虽然学习起来比较费劲,但我希望你能在一次性项目,甚至是生产应用程序中尝试一下 XState 和状态图。到目前为止,我只是为了熟悉它而使用它,但我期待着在生产项目中使用它。即使还没有在生产项目中使用过它,我也确信我的代码已经有所改进,因为我开始从不同的角度看待问题和状态。
与命令式代码相比,设置状态机的初始成本可能需要更长的时间。从长远来看,当添加更多功能并(最后一刻)提出更改请求时,状态机将为您编写和维护代码提供坚实的基础。
对我来说,使用 XState 或状态图最重要的一点是,它是声明式的、显式的,很难创建无效状态。它的另一个优点是其相关的工具以及跨框架的可移植性。
更多资源
- XState 文档
- Erik Mogensen - 欢迎来到 Statecharts 的世界
- David Khourshid - 反应式状态机和状态图 | Uphill Conf 2019
- David Khourshid - 使用有限自动机和状态图简化复杂的 UI | JSConf Iceland 2018
- David Khourshid - 反应式状态机和状态图 | ReactiveConf 2018
- Shawn McKay - 使用 XState v4 绘制应用程序 | React Van
- 与 Jason 一起学习 - 让我们与 David K. Piano 一起学习状态机!
在 Twitter 上关注我@tim_deschryver | 订阅新闻通讯| 最初发布于timdeschryver.dev。
文章来源:https://dev.to/timdeschryver/my-love-letter-to-xstate-and-statecharts-287b