- 前端
- 后端
【免费下载链接】xstate
State machines, statecharts, and actors for complex logic
@xstate/solid是 XState 官方为 SolidJS 提供的绑定包,它把状态机(machine)、状态图(statechart)和 Actor 模型无缝接入 SolidJS 的细粒度响应式体系,让snapshot像原生 Signal 一样被跟踪、触发最小粒度的视图更新。读完本文,你将掌握useActor、useActorRef、fromActorRef等核心 Hook 的完整用法,并理解其底层基于 SolidJS Store 的响应式实现原理,能够直接在本仓库的packages/xstate-solid基础上写出类型安全、可持久化的状态驱动组件。
一、@xstate/solid 是什么
@xstate/solid是 XState 官方维护的 SolidJS 工具包,提供在 SolidJS 组件中解释(interpret)状态机、订阅快照变化、发送事件的能力。它与@xstate/react、@xstate/vue、@xstate/svelte同属 XState 生态的框架绑定层,针对 SolidJS 的响应式模型做了专门优化。
从 包结构 可以看到,该包的实现非常精简,核心源码集中在src/目录下:
- src/index.ts:统一导出四个公开 API ——
useActor、useActorRef、useMachine、fromActorRef; - src/useActor.ts:组合式 Hook,返回快照、发送函数与 actorRef 三元组;
- src/useActorRef.ts:仅创建并托管 actor 生命周期,返回静态的 actorRef;
- src/fromActorRef.ts:把已有 actor 的快照转换为 SolidJS 响应式值;
- src/useMachine.ts:
useActor的别名,保留传统命名习惯; - src/createImmutable.ts 与 src/deepClone.ts:实现快照到 SolidJS Store 的不可变映射与差分更新。
按照 package.json,该包的 peerDependencies 要求solid-js ^1.6.0,并以xstate(当前工作区版本为 v5 系列)作为可选的 peer 依赖,声明"sideEffects": false,可安全地被打包器做摇树优化。
二、快速开始
安装
npm i xstate @xstate/solid@xstate/solid需要与xstate本体一起安装。安装完成后即可在 SolidJS 组件中使用。
第一个状态机组件:Toggle
官方 README 给出了最经典的入门示例 —— 一个双状态切换按钮:
import { useActor } from '@xstate/solid'; import { createMachine } from 'xstate'; const toggleMachine = createMachine({ id: 'toggle', initial: 'inactive', states: { inactive: { on: { TOGGLE: 'active' } }, active: { on: { TOGGLE: 'inactive' } } } }); export const Toggler = () => { const [snapshot, send] = useActor(toggleMachine); return ( <button onclick={() => send({ type: 'TOGGLE' })}> {snapshot.value === 'inactive' ? 'Click to activate' : 'Active! Click to deactivate'} </button> ); };这里的关键点在于:snapshot是一个被 SolidJS 追踪的响应式值。在 JSX 中读取snapshot.value时,SolidJS 会建立细粒度的依赖追踪,状态机每完成一次状态转移,只有读取了对应值的 DOM 部分会更新,而不是整个组件重渲染 —— 这正是 SolidJS 与@xstate/solid结合的核心优势。
三、核心 API 详解
useActor(logic, options?)
useActor(logic, options?)是使用频率最高的 Hook:它解释(interpret)传入的logic,创建一个 actor,并让该 actor 在组件的整个生命周期内运行。
参数
logic:任何 XState 逻辑,包括createMachine(...)创建的状态机,也可以是fromTransition、fromCallback、fromPromise等其他 actor 逻辑;options(可选):ActorOptions,与 XState 的createActor接受的选项一致,典型如input、snapshot、system等。
返回值:一个形如[snapshot, send, actorRef]的元组:
| 返回值 | 说明 |
|---|---|
snapshot | 当前逻辑的快照,只读值,由 SolidJS 进行细粒度响应式追踪 |
send | 向运行中的 actor 发送事件的函数 |
actorRef | 被创建的 actor 引用,可用于手动订阅、停止等操作 |
从 src/useActor.ts 的源码可以看到它的实现非常简洁:
const actorRef = useActorRef(logic, options) as AnyActorRef; return [fromActorRef(actorRef)(), actorRef.send, actorRef as any];也就是说,useActor内部是useActorRef(负责创建 actor)与fromActorRef(负责把快照变成响应式值)的组合:snapshot来自fromActorRef(actorRef)(),send直接透传actorRef.send,actorRef原样返回。这也解释了为什么三个返回值之间具有天然的联动性。
useMachine —— 传统命名的别名
src/useMachine.ts 只是useActor的一个别名(源码中标注了@alias useActor):
export function useMachine<TMachine extends AnyStateMachine>( machine: TMachine, ...[options]: ConditionalRequired<...> ) { return useActor(machine, options); }在 XState v4 时代,useMachine是主流命名;到了 v5,官方统一收口到useActor。如果你在迁移旧代码或更习惯传统写法,useMachine依然可用,但它只接受AnyStateMachine(状态机),而useActor接受更宽泛的AnyActorLogic。
useActorRef(logic, options?)
如果你只需要 actor 引用、不关心快照的响应式更新,可以使用useActorRef(logic, options?)。它与useActor的生命周期相同(创建、启动、随组件卸载而停止),但返回的是静态的 actorRef 引用,不会因快照变化而触发重渲染。
参数
logic:状态机或其他 actor 逻辑;options(可选):ActorOptions。
基本用法:
import { useActorRef } from '@xstate/solid'; import { someMachine } from '../path/to/someMachine'; const App = () => { const actorRef = useActorRef(someMachine); // ... };传入input选项,把外部数据注入状态机的初始上下文:
const App = () => { const service = useActorRef(someMachine, { input: {/* ... */} }); // ... };从 src/useActorRef.ts 的实现可以看到 actor 生命周期的托管方式:
const actorRef = createActor(logic, options); onMount(() => { actorRef.start(); onCleanup(() => actorRef.stop()); }); return actorRef as any;底层直接调用 XState 的createActor(logic, options),在 SolidJS 的onMount阶段调用start()启动,并在组件卸载时通过onCleanup调用stop()停止 —— 这保证了 actor 内部的计时器、订阅、invoke 的子 actor 都会得到正确的清理,不会产生内存泄漏。
fromActorRef(actorRef)
fromActorRef(actorRef)用于订阅一个已存在的 actor 发出的快照变化,并把它转换为 SolidJS 响应式值。这里的参数可以是 actor 对象,也可以是返回 actor 的 SolidJS Signal(或函数),从而支持动态切换 actor:
const snapshot = fromActorRef(someSpawnedActor);如果传入 Signal:
const [actorSignal] = createSignal(someActor); const snapshot = fromActorRef(actorSignal);从 src/fromActorRef.ts 源码看,它的实现依赖createMemo、createEffect和内部工具createImmutable:
- 先用
createMemo解析出当前的 actor(兼容"函数 / 直接传值"两种形态); - 用
createImmutable建立一个以 actor 当前快照为初始值的响应式 Store; - 用
createEffect订阅当前 actor 的next事件,每次收到新快照就更新 Store; - 在
createEffect内注册onCleanup(unsubscribe),当 actor 切换或组件卸载时自动取消订阅。
一个值得注意的细节是:当 actor 切换时,effect 会先同步读取新 actor 的当前快照再建立订阅,避免切换瞬间出现旧值残留。源码中对快照的处理是"每次next都替换整个快照对象",因此开发者不需要手动做浅比较。
四、状态匹配:处理层级与并行状态机
当使用 层级状态机 或并行状态机时,snapshot.value不再是字符串,而是嵌套的对象,例如{ loading: 'user' }。此时字符串比较会失效,官方推荐使用 XState 内置的snapshot.matches(...)方法做模式匹配。
SolidJS 的Switch与Match组件非常适合与matches配合,README 给出了一个数据加载器的典型写法:
const Loader = () => { const [snapshot, send] = useActor(/* ... */); return ( <div> <Switch fallback={null}> <Match when={snapshot.matches('idle')}> <Loader.Idle /> </Match> <Match when={snapshot.matches({ loading: 'user' })}> <Loader.LoadingUser /> </Match> <Match when={snapshot.matches({ loading: 'friends' })}> <Loader.LoadingFriends /> </Match> </Switch> </div> ); };Switch会按顺序匹配第一个when为真的分支,fallback处理都不匹配的情况。由于snapshot是响应式的,当状态机从idle转移到{ loading: 'user' }时,Switch会自动切换渲染对应的分支,无需手动管理条件逻辑。
在 test/useActor.test.tsx 中,官方测试同样大量使用了Switch/Match/Show配合snapshot.matches(...)来断言不同状态下的渲染内容,这验证了该模式在生产代码中的正确性。
五、状态持久化与恢复(Persisted and Rehydrated State)
useActor(...)通过options.snapshot支持状态持久化与恢复:传入之前保存的快照,actor 会从该快照恢复,而不是从机器的初始状态重新开始。
// 从 localStorage 读取持久化的快照配置;取不到时回退到机器的初始快照 const persistedSnapshot = JSON.parse(localStorage.getItem('some-persisted-state-key')) || someMachine.initialState; const App = () => { const [snapshot, send] = useActor(someMachine, { snapshot: persistedSnapshot }); // snapshot 会恢复为传入的持久化快照,而不是机器的初始快照 return (/* ... */); }这在"刷新页面后恢复应用状态"的场景中非常实用:状态机的值、上下文(context)以及已激活的子 actor 状态都能被保存和恢复。
该能力同样得到了测试的验证:test/useActor.test.tsx 中通过createActor(...).start()驱动一个 fetch 状态机进入success状态,调用actorRef.getPersistedSnapshot()拿到持久化快照,再把该快照作为options.snapshot传给useActor(fetchMachine.provide({...}), { snapshot: persistedState }),并断言组件直接渲染出持久化后的结果。测试里还展示了fetchMachine.provide({ actors: { fetchData: fromPromise(mergedProps.onFetch) } })这种在组件内动态注入 actor 实现的组合用法。
需要说明的是,持久化快照必须与状态机结构兼容(状态值、上下文类型一致),否则恢复时可能出现状态值不合法的情况,使用时应对存储内容做校验,或像示例中那样提供someMachine.initialState作为兜底。
六、actorRef 的深入使用:手动订阅
useActor(logic)返回的第三个值是actorRef,它代表正在运行的服务实例。你可以直接对它做细粒度控制:
const [snapshot, send, actorRef] = useActor(someMachine);配合 SolidJS 的createEffect订阅快照变化,并用onCleanup在组件卸载时取消订阅:
createEffect(() => { const subscription = actorRef.subscribe((snapshot) => { // 简单的快照日志 console.log(snapshot); }); onCleanup(() => subscription.unsubscribe()); }); // 注意:actor 引用不应在生命周期内变化由于useActor创建的 actorRef 在组件生命周期内是稳定不变的,所以这里的createEffect只会在挂载时执行一次订阅,onCleanup则确保卸载时正确释放订阅。README 特别强调了这一点:"service should never change"。
七、响应式原理:快照如何进入 SolidJS Store
@xstate/solid与 React 版本最大的区别在于响应式模型。理解其内部实现,有助于你在性能敏感场景做出正确选择。
从 src/fromActorRef.ts 可以看到,快照被存进一个由 src/createImmutable.ts 创建的 SolidJS Store 中。createImmutable的实现基于 SolidJS 作者 Ryan Carniato 的createImmutable原型思路(源码注释中明确标注了这一点),核心流程是:
- 深克隆初始值:用 src/deepClone.ts 的
deepClone把初始快照复制进createStore,避免直接共享外部对象引用; - 差分更新:当收到新的快照时,
updateStore递归对比新旧值,只对变化的部分调用 Store 的set,并用batch批量提交,从而把 DOM 更新范围压缩到最小; - 循环引用保护:
deepClone使用WeakMap记录已克隆的对象引用,防止循环引用导致栈溢出(createImmutable的差分逻辑也用了valueRefs避免重复 diff 同一对象)。
另外,deepClone只深拷贝普通对象和数组(isWrappable判断),类实例会按原引用拷贝,这保证了像 Date、自定义类等值不会被破坏。
这一层设计带来的实际效果是:即使状态机的 context 是一个深层嵌套的大对象,每次转移也只触发读取了变化路径的订阅者更新 —— 这正是"细粒度响应式"在状态机场景下的落地。
八、类型安全与项目约定
@xstate/solid的 API 在类型层面与 XState v5 深度集成。从 src/useActor.ts 可以看到:
- 返回值类型为
[SnapshotFrom<TLogic>, (event: EventFromLogic<TLogic>) => void, ActorRefFrom<TLogic>],即快照类型、事件类型、actor 引用类型全部从TLogic推导; - 第二个参数使用了
ConditionalRequired与RequiredActorOptionsKeys:当逻辑(如fromPromise、fromCallback)强制要求某些选项(如input)时,options会被推导为必填;否则为可选。也就是说,如果你漏掉了必需的input,TypeScript 会在编译期直接报错。
因此,在组件中使用状态机时,建议始终为createMachine提供types泛型声明(上下文、事件、actor 类型),以获得端到端的类型推导与编译期校验。
该包的测试覆盖也相当完整,test/ 目录下的 useActor.test.tsx、useActorRef.test.tsx、fromActorRef.test.tsx、deepClone.test.ts 与 selector.test.tsx 分别验证了 Hook 的渲染行为、生命周期清理、动态 actor 切换、深克隆正确性以及选择器场景,可作为你阅读和扩展该包时的行为基准。
九、小结:如何选择 API
| 场景 | 推荐 API |
|---|---|
| 组件内运行状态机,需要响应式快照与发送事件 | useActor(logic, options?) |
| 只需要 actor 引用,手动管理订阅/副作用 | useActorRef(logic, options?) |
订阅已存在的(如spawn产生的)actor 快照 | fromActorRef(actorRef) |
| 迁移旧代码、习惯传统命名 | useMachine(machine, options?) |
| 层级/并行状态机的状态分支渲染 | Switch+Match+snapshot.matches(...) |
| 刷新后恢复状态 | useActor(logic, { snapshot }) |
@xstate/solid用不足十个源码文件,就完成了 XState 复杂逻辑与 SolidJS 细粒度响应式的桥接:useActor负责组合,useActorRef负责生命周期,fromActorRef负责响应式订阅,createImmutable负责高效差分更新。理解了这条调用链,你就可以在自己的 SolidJS 项目中放心地用它来承载表单、数据加载、复杂交互流程等状态密集型逻辑。
- 前端
- 后端
【免费下载链接】xstate
State machines, statecharts, and actors for complex logic
相关推荐
在 Svelte 中使用 XState 状态机:@xstate/svelte 完整接入指南
在 Svelte 中使用 XState 状态机:@xstate/svelte 完整接入指南 XState 是一套用于表达复杂逻辑的状态机(state machi
前端后端Bunster命令行参数解析:告别手动flag处理
Bunster命令行参数解析:告别手动flag处理 你是否还在为Shell脚本中繁琐的命令行参数解析而烦恼?手动处理 h 、 help 等标志不仅容易出错,还会
前端后端@xstate/react 实战指南:在 React 中用 useMachine 与 useSelector 集成 XState 状态机
@xstate/react 实战指南:在 React 中用 useMachine 与 useSelector 集成 XState 状态机 @xstate/rea
前端后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考