You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 15:56:18