使用泛型自引用静态类型化状态机的状态键
问题描述
我为聊天机器人自动化库开发了有限状态机助手,用于引导用户完成对话的不同阶段。目前功能正常,但想优化静态类型体验:当用户通过状态机描述符创建状态机时,stateMachine.setState()方法只能接受描述符里定义的状态键(比如示例中的"state1"|"state2"),不能是任意字符串。
当前类型定义里StateId设为string,导致setState参数类型太宽。试过多种方案都报TypeScript错误,只能暂时用字符串类型,求正确的泛型实现方式。
当前类型定义:
type StateId = string; type State = { onMessage: ( requester: MessageObj, stateMachineInstance: StateMachineInstance ) => any; } type StateMachineDescriptor = { initialState: StateId; states: { [stateId: string]: State, } }; type StateMachineInstance = StateMachineDescriptor & { currentState: StateId; setState: (newState: StateId) => void; reset: () => void; };
期望的使用示例:
const myStateMachine = createStateMachine({ initialState: "state1", states: { state1: { onMessage: (requester: MessageObj, stateMachine: StateMachineInstance) => { requester.reply("hello1"); stateMachine.setState("state2"); } }, state2: { onMessage: (requester: MessageObj, stateMachine: StateMachineInstance) => { requester.reply("hello2"); stateMachine.setState("state1"); } } } });
解决方案
核心是用泛型捕获状态机描述符里的状态键类型,让setState的参数类型自动约束为这些键的联合类型。具体改造步骤如下:
- 给
StateMachineDescriptor添加泛型参数S extends string,用来表示状态键的类型:
type StateMachineDescriptor<S extends string> = { initialState: S; states: Record<S, State<S>>; };
- 改造
State类型,同样引入泛型S,让onMessage里的stateMachineInstance类型绑定当前状态机的状态键:
type State<S extends string> = { onMessage: ( requester: MessageObj, stateMachineInstance: StateMachineInstance<S> ) => any; };
- 调整
StateMachineInstance的泛型定义,让currentState和setState的参数都约束为S:
type StateMachineInstance<S extends string> = StateMachineDescriptor<S> & { currentState: S; setState: (newState: S) => void; reset: () => void; };
- 最后给
createStateMachine函数添加泛型推导,让TypeScript自动从传入的描述符中推断出状态键类型:
function createStateMachine<S extends string>(descriptor: StateMachineDescriptor<S>): StateMachineInstance<S> { // 状态机实例实现逻辑,这里给出基础示例 return { ...descriptor, currentState: descriptor.initialState, setState(newState) { this.currentState = newState; }, reset() { this.currentState = descriptor.initialState; } }; }
改造完成后,setState仅能接受描述符中定义的状态键,输入其他字符串会直接触发TypeScript类型错误,完全符合需求。
完整可运行示例代码:
// 补充MessageObj基础类型定义 type MessageObj = { reply: (content: string) => void; }; type State<S extends string> = { onMessage: ( requester: MessageObj, stateMachineInstance: StateMachineInstance<S> ) => any; }; type StateMachineDescriptor<S extends string> = { initialState: S; states: Record<S, State<S>>; }; type StateMachineInstance<S extends string> = StateMachineDescriptor<S> & { currentState: S; setState: (newState: S) => void; reset: () => void; }; function createStateMachine<S extends string>(descriptor: StateMachineDescriptor<S>): StateMachineInstance<S> { return { ...descriptor, currentState: descriptor.initialState, setState(newState) { this.currentState = newState; }, reset() { this.currentState = descriptor.initialState; } }; } // 使用示例 const myStateMachine = createStateMachine({ initialState: "state1", states: { state1: { onMessage: (requester, stateMachine) => { requester.reply("hello1"); stateMachine.setState("state2"); // 合法 // stateMachine.setState("state3"); // 触发TypeScript错误:"state3"不在状态键集合中 } }, state2: { onMessage: (requester, stateMachine) => { requester.reply("hello2"); stateMachine.setState("state1"); // 合法 } } } });
内容的提问来源于stack exchange,提问作者Tramonta
相关产品推荐
相关产品推荐

