如何在React项目中监听xState子状态变化并触发自定义逻辑
xState外部监听服务执行完成的实现方案
问题场景
接手使用xState的React项目时,需要在saving.saving子状态下的updateService服务执行完成时,从外部触发自定义打印逻辑,原有状态机结构如下:
states : { saving: { id: 'saving', initial: 'saving', states: { saving: { invoke: { src: 'updateService', onDone: [ { target: 'fetchSchooldays', actions: 'setKursus', cond: 'dateChange', }, { target: '#idle', actions: 'setKursus', }, ], onError: [ // 其他逻辑 ], }, }, fetchSchooldays: { invoke: { // 其他逻辑 }, }, }, }, }
React侧通过如下方式调用状态机:
const [state, send, kursusService] = useMachine(/* 传入对应状态机 */);
之前尝试新增saveDone中间瞬态实现监听,遇到矛盾:移除跳转#idle的配置时可以检测到状态,但状态无法正常流转到根级idle;保留该配置时状态机直接跳过该瞬态,无法触发检测逻辑。
可行实现方式
1. 最推荐:无侵入追加Action,不改动原有状态流转
xState的过渡支持配置多个Action,无论onDone后走哪个分支跳转,都会先执行绑定的Action逻辑,完全不需要调整原有状态结构:
- 首先在状态机的
actions配置中新增自定义逻辑:
// 状态机配置项 { // 其他配置保留 actions: { // 原有setKursus逻辑保持不变 setKursus: (context, event) => { /* 原有业务逻辑 */ }, // 新增服务完成后的自定义逻辑 logUpdateFinish: () => { console.log('updateService执行完成'); } } }
- 调整
saving.saving下invoke.onDone的两个分支,给actions追加自定义方法,actions支持传入数组按顺序执行:
onDone: [ { target: 'fetchSchooldays', actions: ['logUpdateFinish', 'setKursus'], cond: 'dateChange', }, { target: '#idle', actions: ['logUpdateFinish', 'setKursus'], }, ],
2. 零修改状态机:组件侧监听状态变化
如果不想改动状态机内部配置,可以直接在React组件侧通过useEffect监听状态变更,判断状态流转节点触发逻辑:
import { useRef, useEffect } from 'react'; import { useMachine } from '@xstate/react'; const [state, send, kursusService] = useMachine(/* 你的状态机 */); // 用ref存储上一次的状态,用于对比流转过程 const prevStateRef = useRef(state); useEffect(() => { const prevState = prevStateRef.current; // 判断规则:上一步处于saving.saving状态,当前已离开该状态,且不是错误分支跳转 const wasInSavingSubState = prevState.matches({ saving: 'saving' }); const hasLeftSavingSubState = !state.matches({ saving: 'saving' }); const isNotErrorBranch = !state.context.saveError; // 错误字段替换为你项目中实际存储报错的字段名 if (wasInSavingSubState && hasLeftSavingSubState && isNotErrorBranch) { console.log('updateService执行完成'); // 这里写其他自定义逻辑即可 } // 更新ref为当前状态,供下一次对比 prevStateRef.current = state; }, [state]);
3. 修正中间瞬态的用法
之前新增saveDone瞬态失效的核心原因是:瞬态(仅配置空事件''过渡、无停留逻辑的状态)会在同一次状态更新批次中完成所有跳转,外部无法捕获到中间的瞬态值。如果要使用瞬态方案,需要把自定义逻辑绑定到瞬态的entry钩子上,而不是靠外部检测状态:
- 调整
saveDone状态配置,给进入状态的瞬间加执行逻辑:
saveDone : { entry: 'logUpdateFinish', // 进入该状态立刻执行逻辑,哪怕后续马上跳转 on : { '' : [ { target: 'fetchSchooldays', cond: 'dateChange', }, { target : '#idle'} ] } },
- 把原来
onDone两个分支的target统一改为'saveDone'即可,原有流转逻辑不会受影响。
内容的提问来源于stack exchange,提问作者Henrik Clausen
相关产品推荐
相关产品推荐

