XState中machine.transition()未触发action的问题求助
XState exit动作未触发的问题解决
问题重现
编写了如下XState状态机:
import { createMachine } from 'xstate' export const machine = createMachine( { predictableActionArguments: true, schema: { context: {} as { elapsed: number }, events: {} as { type: 'AT_SAFE_ZONE' } | { type: 'WALKING' }, }, initial: 'a', states: { a: { on: { WALKING: { target: 'b', }, }, }, b: { on: { AT_SAFE_ZONE: { target: 'a', }, }, exit: ['calculateWalk'], }, }, }, { actions: { calculateWalk: async (context, event) => { console.log('>>> calculateWalk', context, event) }, }, } )
执行以下代码时,状态能从a切换到b再回到a,但状态b的exit动作calculateWalk从未被调用:
const { initialState } = machine const nextState = machine.transition(initialState, { type: 'WALKING' }) console.log(nextState.value) const nextState2 = machine.transition(nextState, { type: 'AT_SAFE_ZONE' })
在Vitest测试中,其他日志正常输出,仅calculateWalk无日志,尝试多种配置(单动作/动作数组、不同位置配置动作)均无效。
问题原因
machine.transition()是纯函数式的状态计算方法,它仅负责根据当前状态和输入事件计算出下一个状态,不会自动执行任何动作(包括entry/exit动作、过渡动作等)。要让状态机的动作被执行,必须使用XState提供的**解释器(interpreter)**来运行状态机。
解决办法
1. 使用解释器运行状态机
修改执行代码,用interpret创建状态机服务并启动,通过service.send()发送事件:
import { createMachine, interpret } from 'xstate'; import { machine } from './your-machine-path'; // 创建并启动状态机服务 const service = interpret(machine) .onTransition(state => console.log('当前状态:', state.value)) .start(); // 发送事件触发状态切换 service.send({ type: 'WALKING' }); // 从a切换到b service.send({ type: 'AT_SAFE_ZONE' }); // 从b切换到a,此时会执行b的exit动作calculateWalk // 完成后停止服务 service.stop();
2. Vitest测试中的正确写法
在测试中,需要用解释器驱动状态机,并通过spy监听动作是否被调用:
import { describe, it, expect, vi } from 'vitest'; import { interpret } from 'xstate'; import { machine } from './your-machine-path'; describe('状态机exit动作测试', () => { it('退出状态b时应执行calculateWalk动作', async () => { // 监听calculateWalk动作函数 const calculateWalkSpy = vi.spyOn(machine.options.actions!, 'calculateWalk'); // 创建并启动服务 const service = interpret(machine).start(); // 发送事件触发状态切换 service.send({ type: 'WALKING' }); service.send({ type: 'AT_SAFE_ZONE' }); // 断言动作已被调用 expect(calculateWalkSpy).toHaveBeenCalled(); // 清理资源 service.stop(); calculateWalkSpy.mockRestore(); }); });
内容的提问来源于stack exchange,提问作者Rodrigo
相关产品推荐
相关产品推荐

