如何实现带编译期校验的TypeScript状态机?
TypeScript 编译期强制校验的状态机实现方案
需求说明
想要实现一个由TypeScript编译器强制校验状态转换的状态机,期望的定义方式如下:
type States = { state1: {state: 'state1'}, state2: {state: 'state2'} } const Transitions: TransitionsType<States> = { init() { return {state: 'state1'} }, state1: { state2({...anyOtherItems}) { // 处理额外数据 return {...anyOtherItems, state: 'state2'} } }, state2: { state1({...others}) { return {state: 'state1'} } } }; transition(Transitions, {state: 'state1'}, 'state2');
核心需求:
States类型描述每个状态对应的数据结构Transitions对象以源状态为第一层键,目标状态为第二层键,转换函数接收源状态类型参数,返回目标状态类型参数;init函数负责初始化状态- 当转换函数返回错误的状态对象,或者调用
transition时传入非法的状态转换组合,编译器必须抛出错误
现有实现的问题
当前代码存在两个关键问题:
return toTransition(state);行出现编译错误,因为TypeScript无法推断toTransition的参数和返回值类型匹配- 调用
transition传入非法转换(比如从state1到不存在的state3)时,编译器不会报错
修正后的实现方案
下面是可以满足需求的完整实现:
类型定义部分
// 确保每个状态对象都包含对应的state字面量类型 type StateObject<State extends string> = { state: State }; // 定义状态机的状态映射:键是状态名,值是对应状态对象类型 type StateMap = { [K in string]: StateObject<K> }; // 单个源状态下的所有合法转换:键是目标状态名,值是转换函数 type StateTransitions<S extends StateMap, From extends keyof S> = { [To in keyof S]?: (currentState: S[From]) => S[To]; }; // 完整的状态转换配置类型 type TransitionsType<S extends StateMap> = { [From in keyof S]: StateTransitions<S, From>; } & { init: () => S[keyof S]; };
核心transition函数实现
// 利用泛型约束,让编译器在编译期检查转换合法性 function transition< S extends StateMap, From extends keyof S, To extends keyof S >( transitions: TransitionsType<S>, currentState: S[From], to: To ): S[To] { const fromTransitions = transitions[currentState.state]; const transitionFn = fromTransitions?.[to]; if (!transitionFn) { throw new TypeError(`不存在从 ${currentState.state} 到 ${String(to)} 的转换`); } // 这里类型完全匹配,不会有编译错误 return transitionFn(currentState); }
效果验证
用之前的States和Transitions测试:
- 如果转换函数返回错误的状态(比如
state1的state2函数返回{state: 'state1'}),编译器会直接报错,因为返回值类型不匹配S['state2'] - 如果调用
transition(Transitions, {state: 'state1'}, 'state3'),编译器会报错,因为state3不是States的合法键,且Transitions中没有对应的转换定义 transition函数的返回值类型会被正确推断为目标状态的类型,比如transition(Transitions, {state: 'state1'}, 'state2')的返回值类型是States['state2']
关键改进点
- 用更精确的泛型参数
From和To,让编译器能追踪当前状态和目标状态的类型 - 约束
transition函数的currentState必须是S[From],to必须是keyof S,同时确保转换函数的参数和返回值类型严格对应 - 消除了原代码中的类型断言,让类型推导完全由编译器自动完成
内容的提问来源于stack exchange,提问作者Rahul D'Souza
相关产品推荐
相关产品推荐

