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

如何编写接口确保子类返回自身类型?TypeScript泛型问题

TypeScript 约束接口方法返回自身类型的解决方案

问题场景与报错原因

先看原始代码及报错:

interface IXY {
  x: number;
  y: number;

  scale(n: number): IXY;
}

interface IXYZ extends IXY {
  z: number;

  scale(n: number): IXYZ;
}

// 非真实代码,仅作示例
function flip<T extends IXY>(v: T, flip: boolean): T {
  if (flip) return v.scale(-1); // 此处报错
  else return v;
}

报错信息:

Type 'IXY' is not assignable to type 'T'.
'IXY' is assignable to the constraint of type 'T',
but 'T' could be instantiated with a different subtype of constraint 'IXY'

报错的核心原因是:IXY的继承接口仅需保证scale返回IXY或其子类型,无需返回自身。比如可以定义这样的子类:

interface IABC extends IXY {
  scale(n: number): IXY; // 无需返回IABC自身
}

我们的需求是:强制所有继承IXY的接口,其scale方法必须返回自身类型;调用flip传入IXYZ时,返回值必须保留z属性,类型仍为IXYZ。

之前尝试用this类型实现,但类的实现会报错:

interface IXY {
  x: number;
  y: number;

  scale(n: number): this;
}

interface IXYZ extends IXY {
  z: number;

  scale(n: number): this;
}

class XY implements IXY {
  x: number;
  y: number;

  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }

  scale(n: number): this { // 无法编译
     return new XY(this.x * n, this.y * n);
  }

  scale(n: number): XY { // 同样无法编译
     return new XY(this.x * n, this.y * n);
  }
}

// 非真实代码,仅作示例
function flip<T extends IXY>(v: T, flip: boolean): T {
  if (flip) return v.scale(-1);
  else return v;
}

解决方案:递归泛型接口约束

通过递归泛型定义基础接口,强制子类的scale方法返回自身类型。

1. 定义泛型基础接口

把IXY改为泛型接口,用递归约束保证scale返回当前类型:

interface IXY<T extends IXY<T>> {
  x: number;
  y: number;
  scale(n: number): T;
}

这里T extends IXY<T>的递归约束,确保T必须是实现了IXY<T>的类型,直接限制了scale的返回值必须为自身类型。

2. 定义子接口

继承时将自身作为泛型参数传入:

interface IXYZ extends IXY<IXYZ> {
  z: number;
  // 无需重复定义scale,继承自IXY<IXYZ>的scale会自动返回IXYZ类型
}

3. 实现类的正确写法

实现泛型接口时,指定自身为泛型参数,并且用this.constructor创建实例(兼容子类继承场景):

class XY implements IXY<XY> {
  x: number;
  y: number;

  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }

  scale(n: number): XY {
    // 使用this.constructor创建实例,保证子类继承时也能返回自身类型
    return new (this.constructor as typeof XY)(this.x * n, this.y * n);
  }
}

class XYZ extends XY implements IXYZ {
  z: number;

  constructor(x: number, y: number, z: number) {
    super(x, y);
    this.z = z;
  }

  scale(n: number): XYZ {
    return new (this.constructor as typeof XYZ)(this.x * n, this.y * n, this.z * n);
  }
}

4. 修正flip函数

现在flip函数可以正确推导返回类型,不再报错:

function flip<T extends IXY<T>>(v: T, flip: boolean): T {
  if (flip) return v.scale(-1);
  return v;
}

// 测试验证:传入XYZ实例,返回值仍为XYZ类型
const xyz = new XYZ(1, 2, 3);
const flippedXyz = flip(xyz, true);
console.log(flippedXyz.z); // 可正常访问z属性,类型推导正确

关于this类型报错的说明

接口中用this类型表示返回当前实例类型,但类的scale方法中直接返回new XY(...)时,返回的是XY实例,而子类中的this类型是子类本身,两者类型不匹配导致报错。用this.constructor创建实例,能保证返回当前类的实例,配合泛型接口的约束,即可满足类型要求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 22:36:33