@xstate/react 核心 API 演进指南:从 useMachine 到 useActor 与 ActorRef 的 React 状态机实践
发布时间:2026/9/30 8:14:06来源:尧图网络
前端后端【免费下载链接】xstateState machines, statecharts, and actors for complex logic项目地址https://gitcode.com/gh_mirrors/xs/xstate点击查看免费下载xstate/react 是 XState 官方提供的 React 集成包它把状态机State Machines、状态图Statecharts与 Actor 模型无缝接入 React 组件树。本文以该包的 CHANGELOG.md 为主线梳理从 v1 到 v6 的关键 API 变更、破坏性迁移点与最佳实践并对照 packages/xstate-react/src 下的源码实现帮助你在新老代码库中都能正确、高效地使用useActor、useActorRef、useSelector与createActorContext。读完本文你将掌握如何用 Hooks 绑定状态机并驱动组件渲染、如何在错误发生时借助 React Error Boundary 兜底、如何让组件只订阅 Actor 的局部快照避免多余渲染以及如何从旧 API 平滑迁移到新的 Actor 模型。快速上手一个最小可运行示例来自 README.md 的 Quick start 给出了最基础的用法。安装依赖npm i xstate xstate/react然后定义一个简单的 toggle 状态机并通过useMachine绑定到组件import { useMachine } from xstate/react; import { createMachine } from xstate; const toggleMachine createMachine({ id: toggle, initial: inactive, states: { inactive: { on: { TOGGLE: active } }, active: { on: { TOGGLE: inactive } } } }); export const Toggler () { const [state, send] useMachine(toggleMachine); return ( button onClick{() send({ type: TOGGLE })} {state.value inactive ? Click to activate : Active! Click to deactivate} /button ); };useMachine(machine)返回一个元组当前快照state与send发送事件函数。在 v4 之后useMachine本质上是useActor的别名见 useMachine.ts 中的/** alias useActor */注释与直接转调useActor(machine, options)的实现。核心 Hooks API 总览从 src/index.ts 可以看到包当前对外暴露的全部公共 APIcreateActorContext—— 创建与 React Context 集成的全局 ActoruseActor—— 绑定任意 Actor 逻辑机器、Promise、Transition 等返回[snapshot, send, actorRef]useActorRef—— 返回可直接传给其他组件的ActorRef不订阅其快照变化useSelector—— 从 Actor 快照中派生局部状态并支持自定义比较函数shallowEqual—— 浅比较辅助函数useMachine—— 已弃用仅为useActor的别名保留用于向后兼容。useActor 的底层实现在 useActor.ts 中可以看到它如何把 Actor 接入 React 渲染循环通过useIdleActorRef(logic, options)惰性创建 ActorRef首次渲染时才createActor见 useActorRef.ts用useSyncExternalStore订阅 Actor 的快照subscribe与getSnapshot均用useCallback稳定引用在useEffect中启动actorRef.start()卸载时调用stopRootWithRehydration清理。其中getSnapshot直接取actorRef.getSnapshot()因此传入的 Actor 逻辑必须具备getSnapshot方法——这正是 v4.0.0 中“移除 hooks 的getSnapshot参数”这一破坏性变更的前提见下文迁移章节。useActorRef 与订阅解耦useActorRef返回 Actor 引用但不订阅快照因此适合需要把引用传给子组件、由子组件自行选择订阅方式的场景。它还接受第二个参数observerOrListener可直接传入Observer对象或回调函数源码中通过toObserver(observerOrListener)统一转换并在useEffect中完成订阅与清理见 useActorRef.ts。错误传播让 Actor 的 error 状态进入 React Error BoundaryCHANGELOG 6.1.0 是本文最值得关注的能力useActor与useSelector现在会在 Actor 进入 error 状态时主动抛出错误从而可以被 React Error Boundary 捕获。此前错误只会静默停留在快照的status: error字段里需要手动处理。其实现非常直观useActor在拿到快照后检查status in actorSnapshot snapshot.status error命中则throw snapshot.error见 useActor.tsuseSelector的boundGetSnapshot同样在返回前检查并抛出见 useSelector.ts。官方示例——一个可能因网络错误而进入 error 状态的机器import { createMachine } from xstate; import { useActor } from xstate/react; import { ErrorBoundary } from react-error-boundary; const machine createMachine({ initial: idle, states: { idle: { on: { fetch: loading } }, loading: { invoke: { src: fromPromise(async () { throw new Error(Network error); }), onDone: success // Without onError, the actor enters an error state } }, success: {} } }); function App() { return ( ErrorBoundary fallback{pSomething went wrong/p} ActorComponent / /ErrorBoundary ); } function ActorComponent() { // If the actor errors, the error will be thrown // and caught by the nearest error boundary const [snapshot, send] useActor(machine); return div{snapshot.value}/div; }要点只有进入error 状态例如invoke的 Promise 抛出且未定义onError时才会抛错如果机器通过onError显式处理了错误并转移到普通状态则不会触发 Error Boundary。这让“全局兜底 局部容错”两种策略可以按需混用。useSelector局部订阅、自定义比较与可空 Actor选择器与比较函数useSelector(actor, selector, compare?)基于useSyncExternalStoreWithSelector实现见 useSelector.ts默认使用defaultCompare严格相等a b判断选择结果是否变化避免无关状态更新引发组件重渲染const count useSelector(someActor, (state) state.context.count);对于返回新对象的选择器可以传入自定义比较函数如包内导出的shallowEqual实现见 shallowEqual.tsconst { count, list } useSelector( actor, (state) ({ count: state.context.count, list: state.context.list }), shallowEqual );兼容 xstate/store4.1.1从 4.1.1 起xstate/react的useSelector可以直接订阅xstate/store创建的 store无需引入xstate/store/reactimport { createStore } from xstate/store; import { useSelector } from xstate/react; const store createStore( { count: 0 }, { inc: { count: (context) context.count 1 } } ); function Counter() { // Note that this useSelector is from xstate/react, // not xstate/store/react const count useSelector(store, (state) state.context.count); return ( div button onClick{() store.send({ type: inc })}{count}/button /div ); }可空 Actor4.1.0当 Actor 可能尚未创建时例如来自异步逻辑的可选引用useSelector的actor参数允许为undefined此时传给选择器的snapshot也可能是undefinedconst count useSelector(maybeActor, (snapshot) { // snapshot may be undefined return snapshot?.context.count; }); count; // number | undefined对应源码中TActor extends PickAnyActorRef, ... | undefined的泛型约束以及if (!actor) return () {};的空订阅分支见 useSelector.ts。createActorContext全局 Actor 的 React Context 集成基本用法3.1.0 引入createActorContext(logic, options?)返回一个绑定 Actor 的 React Context 对象官方在 3.1.0 中明确其包含.Provider—— React Context Provider.useActor(...)—— 获取当前状态并向 Actor 发送事件v4 起从 context 上移除.useSelector(...)—— 派生状态订阅.useActorRef()—— 获取 Actor 引用。import { createActorContext } from xstate/react; import { someMachine } from ./someMachine; // Create a React Context object that will interpret the machine const SomeContext createActorContext(someMachine); function SomeComponent() { // Get the current state and send function const [state, send] SomeContext.useActor(); // Or select some derived state const someValue SomeContext.useSelector((state) state.context.someValue); // Or get a reference to the actor const actorRef SomeContext.useActorRef(); return (/* ... */); } function App() { return ( SomeContext.Provider SomeComponent / /SomeContext.Provider ); }从当前源码 createActorContext.ts 看createActorContext返回的钩子已收敛为Provider、useActorRef即内部useContext与useSelector三个成员。Provider 的 options 合并4.0.3 / 4.0.0 / 3.2.0Provider组件接受options属性其语义与useMachine(machine, options)的第二个参数一致。3.2.0 将 options 从createActorContext(machine, options)的第二个参数迁移到Provider options{...}上4.0.3 进一步修复了options 合并问题——此前 Provider 的 options 会整体替换创建时的 options现在二者正确合并const { inspect } createBrowserInspector(); const SomeContext createActorContext(someMachine, { inspect }); // ... // Options are now merged: // { inspect: inspect, input: 10 } SomeContext.Provider options{{ input: 10 }} {/* ... */} /SomeContext.Provider;合并逻辑可见于 createActorContext.tsuseActorRef(providedLogic, { ...actorOptions, ...providedOptions })先展开创建时 options再覆盖 Provider 传入的 options。4.0.0 中还移除了createActorContext第三参数observerOrListener并弃用了 Provider 上的machineprop源码中会直接throw提示改用logic见 createActorContext.ts。类型安全types/input 定义后的必填约束4.1.2在 4.1.2 之前即便机器在setup({ types: { input } })中声明了input类型useActor、useMachine、useActorRef也不会在编译期强制要求传入input容易在运行时崩溃。此版本修复后声明即必填const machine setup({ types: { input: {} as { value: number } } }).createMachine({}); function App() { // With this change the above code will show a type error, // since input is now required: const _ useMachine(machine, { input: { value: 1 } // Now input is required at compile time! }); return /; }这得益于 Hooks 签名中的工具类型ConditionalRequired与RequiredActorOptionsKeys当泛型推导出 options 中存在必填键时第二个参数会变为必填见 useActor.ts 与 useActorRef.ts。4.0.0 破坏性变更与迁移清单CHANGELOG 中 4.0.0含 4.0.0-beta 系列是 API 收敛最剧烈的一版迁移时需逐条对照变更旧写法新写法useMachine弃用为useActor别名useMachine(machine)useActor(machine)或继续使用useMachine等价移除useSpawnuseSpawn(machine)useActorRef(machine)移除useActor(actorRef)的旧用法const [state, send] useActor(actorRef)const state useSelector(actorRef, s s)发送用actorRef.send(...)实现actions 等不再作为 options 传入useMachine(machine, { actions: {...} })useMachine(machine.provide({ actions: {...} }))移除useMachine的工厂函数参数useMachine(() createMachine(...))直接传机器对象移除 hooks 的getSnapshot参数useActor(actor, (actor) actor.current)依赖 ActorRef 自带getSnapshot()FSM 相关函数移除xstate/react/fsm相关使用统一的useActorContext 上移除useActorMyCtx.useActor()用MyCtx.useSelectorMyCtx.useActorRef替代关于machine.provide(...)官方特别说明xstate/react会检测机器的 config 是否仍相同因此用provide后不会触发 machine has changed 警告。同时移除工厂函数参数意味着机器实例应在组件外或 memo 后稳定创建以避免每次渲染重新初始化。exports字段也在 v4 中被加入package.jsonmanifest见 package.json限制了可从包导入的文件范围——只能导入公共 API不能再从深层路径引入内部模块。React 严格模式、Fast Refresh 与渲染稳定性CHANGELOG 中多个版本都围绕 React 渲染语义修复问题这些修复对生产环境的稳定性影响显著4.0.1修复 React Strict Mode 下after延迟转换失效的问题。此前严格模式下 effect 的重复挂载/卸载会破坏延迟事件调度修复后after转换在所有 React 模式下都能按预期工作。4.0.0 (Minor)Fast Refresh 在大多数场景下恢复正常工作组件热更新后不再卡在过期的内部快照。3.0.0useMachine在内部服务重启如 Fast Refresh 场景时会以初始状态正确重渲染避免展示与真实服务不一致的陈旧状态。3.0.0xstate/fsm的useMachine改为在 effect 中启动服务避免渲染期副作用并提升 StrictMode 兼容性fsm 的实现更新放入 layout effect规避布局 effect 发事件时的陈旧闭包问题。支撑这些能力的底层是卸载清理函数stopRootWithRehydration见 stopRootWithRehydration.ts它会递归遍历 Actor 树持久化每个 Actor 的快照、清空 observers 后停止根 Actor再恢复快照从而在严格模式的 effect 重连中保持可预测行为。版本演进中的其他重要节点3.0.0将 peer dependency 提升到 React 18并基于use-sync-external-store含 shim重写实现从而兼容更老的 React 版本同时移除asEffect与asLayoutEffectaction creators——官方建议在 effect 中直接执行副作用或向机器发送事件后再由机器响应动作。2.0.0TypeScript 4.0 开始支持 typegen移除已弃用的useService统一由useActor替代Hooks 的泛型收敛为单一TMachine。1.3.0引入useInterpret低阶解释器 Hook返回 service与useSelectoruseService宣布弃用。1.1.0spawned/invoked Actor 统一类型化为ActorRef配合ActorRefFromtypeof machine可让子组件安全接收 Actor 引用并保持类型完整state推导为StateSomeContext, SomeEventsend只能发送合法事件。1.0.0-rc.7useMachine曾支持懒创建机器useMachine(() createMachine(...))该能力在 v4 被移除。0.7.0 / 0.7.1早期允许将guards、actions、activities、services、delays、updates等机器配置合并进useMachine(machine, options)且 action 实现会持续保持最新、不引用陈旧数据这一设计最终被 v4 的machine.provide(...)取代。各版本与核心包xstate的依赖关系同样记录在 CHANGELOG 中例如 5.0.0 依赖xstate5.19.0、4.1.3 依赖xstate5.18.2、4.0.0 依赖xstate5.20.0。从 5.0.3 / 5.0.0 起React 19 被加入 peer dependency见 package.json当前包同时支持 React 18 与 19。总结通过 CHANGELOG 与源码的对照可以看到xstate/react的演进主线是一切皆 Actor。从早期面向机器的useMachine/useInterpret到面向任意逻辑的useActor/useActorRef/useSelector再到全局化的createActorContext包的设计始终围绕 ActorRef 的快照订阅与类型安全展开。升级时优先对照本文的 4.0.0 迁移清单把机器实现迁移到machine.provide(...)、把引用订阅改为useSelector、把创建引用改为useActorRef即可平稳过渡到当前的 Actor 模型。赞分享前端后端【免费下载链接】xstateState machines, statecharts, and actors for complex logic项目地址https://gitcode.com/gh_mirrors/xs/xstate点击查看免费下载相关推荐xstate/react 实战指南在 React 中用 useMachine 与 useSelector 集成 XState 状态机xstate/react 实战指南在 React 中用 useMachine 与 useSelector 集成 XState 状态机 xstate/rea前端后端告别状态混乱React状态机方案XState实战指南告别状态混乱React状态机方案XState实战指南 你是否还在为React应用中的状态管理头疼表单验证逻辑混乱、用户交互状态失控、异步操作竞态条件频发本前端教程文档用 XState v5 与 React 实现 TodoMVC从状态机设计到 localStorage 持久化的完整实践用 XState v5 与 React 实现 TodoMVC从状态机设计到 localStorage 持久化的完整实践 导读 本文围绕当前仓库中的 examp前端后端上一篇Cloudflare Agents 中的 Codemode 实战让 LLM 编写代码来编排你的 AI 工具下一篇LeetCode 277 名人问题Find the Celebrity全解暴力枚举、O(n) 逻辑排除与记忆化缓存创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网