递归泛型函数参数类型无法用于泛型调用:求解决方案
解决递归泛型函数中传递运行时类型的问题
这个问题我之前也碰到过,核心是TypeScript里编译时类型和运行时值的本质区别——你想把运行时的memberType变量当作泛型参数用,但泛型是编译期确定的类型信息,直接把值当类型用自然会报错。下面给你具体的解决思路和修正后的代码:
关键思路
要把运行时的构造函数(比如Header、Body)和编译时的实例类型关联起来,我们可以用TypeScript的两个工具类型:
typeof:获取运行时值的类型(这里就是构造函数的类型)InstanceType<T>:从构造函数类型T中提取出实例的类型
同时还要先修正各个类中MEMBERS的类型声明,让TypeScript能准确推断出memberType的类型,避免后续类型推断混乱。
修正后的完整代码
class Header { size: number; ttl: number; // 明确MEMBERS的类型:每个元素是[属性名, 基础类型字符串, 起始偏移, 长度] static MEMBERS: Array<[string, 'number' | 'string', number, number]> = [ ['size', 'number', 0, 2], ['ttl', 'number', 2, 3], ]; } class Body { raw: string; static MEMBERS: Array<[string, 'number' | 'string', number, number]> = [ ['raw', 'string', 0, 10], ]; } class Packet { header: Header; body: Body; // 明确MEMBERS的类型:每个元素是[属性名, 类构造函数, 起始偏移, 长度] static MEMBERS: Array<[string, typeof Header | typeof Body, number, number]> = [ ['header', Header, 0, 6], ['body', Body, 6, 10], ]; } class Deserializer { static Deserialize<T>(serialized: string, type: { new(): T }): T { const obj = new type(); // 断言MEMBERS的类型,避免TypeScript对constructor['MEMBERS']的类型模糊推断 const members = (obj.constructor as typeof Packet | typeof Header | typeof Body).MEMBERS; for (const member of members) { const [memberName, memberType, startOffset, len] = member; const serializedMember = serialized.substr(startOffset, len); if (memberType === 'number') { (obj as Record<string, unknown>)[memberName] = parseInt(serializedMember, 10); } else if (memberType === 'string') { (obj as Record<string, unknown>)[memberName] = serializedMember; } else { // 从构造函数类型中提取实例类型,作为泛型参数 type MemberInstance = InstanceType<typeof memberType>; // 递归调用反序列化,传入子构造函数和对应的子序列化字符串 (obj as Record<string, unknown>)[memberName] = this.Deserialize<MemberInstance>(serializedMember, memberType); } } return obj; } } // 测试代码 const serialized = '11222qwertyuiop'; const packet: Packet = Deserializer.Deserialize<Packet>(serialized, Packet); console.log(packet); // 输出符合预期: // Packet { // header: Header { size: 11, ttl: 222 }, // body: Body { raw: 'qwertyuiop' } // }
代码说明
- 明确MEMBERS类型:给每个类的
MEMBERS加上精确的类型声明,让TypeScript能准确知道memberType要么是基础类型字符串('number'/'string'),要么是具体的类构造函数(typeof Header/typeof Body)。 - 类型断言处理属性赋值:用
(obj as Record<string, unknown>)来绕过TypeScript对动态属性名的类型检查,因为memberName是动态的,TypeScript无法自动推断它对应的属性类型。 - 关联运行时构造函数与编译时类型:通过
InstanceType<typeof memberType>把运行时的构造函数memberType转换成编译时的实例类型,这样就能合法地作为泛型参数传给Deserialize了。
内容的提问来源于stack exchange,提问作者Tesseract
相关产品推荐
相关产品推荐

