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

递归泛型函数参数类型无法用于泛型调用:求解决方案

解决递归泛型函数中传递运行时类型的问题

这个问题我之前也碰到过,核心是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' }
// }

代码说明

  1. 明确MEMBERS类型:给每个类的MEMBERS加上精确的类型声明,让TypeScript能准确知道memberType要么是基础类型字符串('number'/'string'),要么是具体的类构造函数(typeof Header/typeof Body)。
  2. 类型断言处理属性赋值:用(obj as Record<string, unknown>)来绕过TypeScript对动态属性名的类型检查,因为memberName是动态的,TypeScript无法自动推断它对应的属性类型。
  3. 关联运行时构造函数与编译时类型:通过InstanceType<typeof memberType>把运行时的构造函数memberType转换成编译时的实例类型,这样就能合法地作为泛型参数传给Deserialize了。

内容的提问来源于stack exchange,提问作者Tesseract

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 19:13:01