处理复杂状态机应分层控制响应粒度:用 ref 管理当前状态(如 status),reactive 存储上下文,结合 XState 声明式迁移,markRaw + shallowRef 隔离不可变实体,并用 watchEffect + computed 统一管理副作用。

处理复杂状态机时,Vue 的响应式 API 不是简单堆砌 ref 或 reactive,而是要分层控制响应粒度、隔离副作用、并让状态迁移可预测。核心在于把“状态机”本身当作一个不可变的逻辑单元,而不是把所有状态变量都暴露为响应式。
用 reactive 封装状态机上下文,但不直接响应内部字段
状态机的状态(如 'idle'、'loading'、'success')和上下文数据(如用户信息、表单值)应分开管理。推荐用 reactive 包裹整个上下文对象,但关键状态字段(如 current)建议用 ref 控制,避免被深层响应干扰迁移逻辑:
- 用 ref 管理当前状态名(例如
const status = ref('idle')),保证状态切换明确、可监听 - 用 reactive 存储派生数据或临时上下文(如
const context = reactive({ user: null, error: null })),便于模板直接访问 - 避免对 context 做 deep watch;需要响应变化时,显式触发 computed 或自定义事件
借助 XState 实现声明式状态迁移,绕过手动响应式拼接
纯靠 ref/reactive 手写状态流转容易出错,尤其涉及并行状态、历史记录或条件跳转。@xstate/vue 的 useMachine 是更稳健的选择:
用于端到端视频本地化流程的轻量编排器,路由至四个专注子技能——/wjs-transcribing-audio、/wjs-translating-subtitles...
- 状态机配置(JSON 或 createMachine)完全脱离 Vue 响应式系统,逻辑清晰、可测试
-
useMachine返回的 state 是只读 proxy,send 是唯一变更入口,天然防止非法状态跃迁 - 模板中通过
state.value === 'done'判断,或用state.matches('loading'),语义比手写 if-else 更可靠
用 shallowRef + markRaw 隔离第三方或不可变状态节点
当状态机中嵌入了非响应式实体(如图表实例、WebSocket 连接、大型缓存对象),直接 reactive 会带来性能开销甚至意外行为:
立即学习“前端免费学习笔记(深入)”;
- 用 markRaw 标记这些对象,确保它们不会被 proxy 包裹
- 再用 shallowRef 持有其引用,仅在整体替换时触发更新(例如重连 WebSocket 实例)
- 这样既保留了响应式驱动的能力,又避免了对内部属性做无意义的 getter/setter 追踪
watchEffect + computed 聚焦“可观测行为”,而非“所有字段”
复杂状态机的副作用(如日志、埋点、API 请求)不应散落在每个 ref 的 watch 里。应集中建模为可观测行为:
- 用 computed 提取复合判断(如
const canSubmit = computed(() => state.value === 'ready' && context.formValid)) - 用 watchEffect 监听 state.value 或 computed 结果,统一触发副作用,避免重复逻辑
- 迁移前的校验、迁移后的清理等生命周期动作,封装进独立函数,由 send 的回调或 useMachine 的 onTransition 统一调用

















