XState/FSM中TState与InitEvent类型定义的TypeScript兼容问题
问题
刚上手@xstate/fsm,声明TState类型时碰到TypeScript兼容性问题,相关代码如下:
import {createMachine, EventObject, StateMachine} from '@xstate/fsm'; import {Typestate} from "@xstate/fsm/lib/types"; export interface InitContext { retries: number; } export interface InitState extends Typestate<InitContext> { value: any; context: InitContext; } export type InitEvent = | { type: "LOG_IN" } | { type: "FETCHED" } | { type: "ERROR" } | { type: "RESTART" }; // 重复定义的InitEvent export type InitEvent = | { type: "LOG_IN" } | { type: "FETCHED" } | { type: "ERROR" } | { type: "RESTART" } machine!: StateMachine.Machine<InitContext, InitEvent, InitState>; machine2!: StateMachine.Machine<InitContext, EventObject, { value: any; context: InitContext }>; this.machine = createMachine<InitContext>({}); // 此代码报错 this.machine2 = createMachine<InitContext>({}); // 此代码正常运行
报错信息:
TS2322: Type 'Machine<InitContext, EventObject, { value: any; context: InitContext; }>' is not assignable to type 'Machine<InitContext, InitEvent, InitState>'. Types of property 'transition' are incompatible. Type '(state: string | State<InitContext, EventObject, { value: any; context: InitContext; }>, event: string | EventObject) => State<...>' is not assignable to type '(state: string | State<InitContext, InitEvent, InitState>, event: "FETCH_CONFIG" | "LOG_IN" | "FETCH_MAIN_DATA" | "FETCH_LINKED_DATA" | "FETCHED" | "ERROR" | "RESTART" | InitEvent) => State<...>'. Types of parameters 'state' and 'state' are incompatible. Type 'string | State<InitContext, InitEvent, InitState>' is not assignable to type 'string | State<InitContext, EventObject, { value: any; context: InitContext; }>'. Type 'State<InitContext, InitEvent, InitState>' is not assignable to type 'string | State<InitContext, EventObject, { value: any; context: InitContext; }>'. Type 'State<InitContext, InitEvent, InitState>' is not assignable to type 'State<InitContext, EventObject, { value: any; context: InitContext; }>'. Types of property 'actions' are incompatible. Type 'ActionObject<InitContext, InitEvent>[]' is not assignable to type 'ActionObject<InitContext, EventObject>[]'. Type 'ActionObject<InitContext, InitEvent>' is not assignable to type 'ActionObject<InitContext, EventObject>'. Type 'EventObject' is not assignable to type 'InitEvent'.
createMachine方法定义:
export declare function createMachine<TContext extends object, TEvent extends EventObject = EventObject, TState extends Typestate<TContext> = { value: any; context: TContext; }>(fsmConfig: StateMachine.Config<TContext, TEvent, TState>, implementations?: { actions?: StateMachine.ActionMap<TContext, TEvent>; }): StateMachine.Machine<TContext, TEvent, TState>;
请问应如何正确定义InitState和InitEvent类型?
解决方案
问题根源
InitEvent被重复定义,属于冗余代码InitState继承Typestate<InitContext>后重复声明context和value,且value用any会丢失类型约束- 调用
createMachine时只传递了TContext类型参数,导致TEvent和TState使用默认值(EventObject和默认Typestate),和声明的machine类型不匹配
修正步骤
- 移除重复的
InitEvent定义:保留一份即可,确保事件类型是EventObject的子集(当前定义已满足,每个事件都包含type属性) - 正确定义
InitState:Typestate<TContext>已包含context: TContext和value字段,只需指定value的具体状态值类型(替换any) - 调用
createMachine时传递完整类型参数:若需显式指定类型,需传递TContext、TEvent、TState三个参数;也可让TypeScript根据状态机配置自动推导
修正后的完整代码
import { createMachine, EventObject, StateMachine } from '@xstate/fsm'; import { Typestate } from "@xstate/fsm/lib/types"; export interface InitContext { retries: number; } // 定义具体的状态值类型,替换any type InitStateValue = 'idle' | 'loggingIn' | 'success' | 'error'; // 正确继承Typestate,无需重复声明context和value export interface InitState extends Typestate<InitContext> { value: InitStateValue; } // 只保留一份InitEvent定义 export type InitEvent = | { type: "LOG_IN" } | { type: "FETCHED" } | { type: "ERROR" } | { type: "RESTART" }; // 声明机器类型 machine!: StateMachine.Machine<InitContext, InitEvent, InitState>; // 调用createMachine时传递完整的三个类型参数,同时补充状态机配置 this.machine = createMachine<InitContext, InitEvent, InitState>({ initial: 'idle', context: { retries: 0 }, states: { idle: { on: { LOG_IN: 'loggingIn' } }, loggingIn: { on: { FETCHED: 'success', ERROR: 'error' } }, success: { on: { RESTART: 'idle' } }, error: { on: { RESTART: 'idle' } } } });
额外说明
- 若不需要显式指定
InitState,可让TypeScript根据配置自动推导TState,此时调用createMachine只需传递TContext和TEvent:this.machine = createMachine<InitContext, InitEvent>({ // 状态机配置 }); - 避免用
any作为value类型,才能利用TypeScript的类型检查确保状态转换的正确性
内容的提问来源于stack exchange,提问作者Leos Literak
相关产品推荐
相关产品推荐

