我有这个代码:

class BaseModel {
  protected getAttribute<T extends BaseModel>(): keyof T | null {
    return null;
  }
}

class Payment extends BaseModel {}
class Item extends BaseModel {}

class Sale extends BaseModel {
  value?: number;
  payment?: Payment;
  items?: Item[];

  protected override getAttribute(): keyof Sale | null {
    return 'payment';
  }
}

这是真实代码的简化版本.getAttribute做一些其他的事情,但解决这个简单的实现将解决我的问题.我需要在父类BaseModel中使用子类自己的实现覆盖函数getAttribute.该函数只需要返回Sale(或BaseModel的任何其他子类)中存在的属性,但我在Sale中得到了这个错误:

Property 'getAttribute' in type 'Sale' is not assignable to the same property in base type 'BaseModel'.
Type '() => keyof Sale | null' is not assignable to type '<T extends BaseModel>() => keyof T | null'.
Type 'keyof Sale | null' is not assignable to type 'keyof T | null'.
Type 'string' is not assignable to type 'keyof T'.
Type 'string' is not assignable to type 'never'.

我试着把Sale中的getAttribute改为:

protected override getAttribute<T extends Sale>(): keyof T | null {
     return 'payment'; 
}  

但也没成功Sale是一个扩展BaseModel的类,所以Sale应该等于T,不是吗?

推荐答案

的问题

class BaseModel {
  protected getAttribute<T extends BaseModel>(): keyof T | null {
    return null;
  }
}

getAttribute()是一个generic方法/函数,其中类型参数T附加到call signature.因此,它与函数参数一起在call site处被指定. 写getAttribute()的人不能 Select T,而是写calls的人. 因此,BaseModel是类,它的getAttribute()方法将接受caller想要的任何TconstrainedBaseModel,并返回keyof Tnull. 事实上,你试图用不起作用的东西覆盖子类中的getAttribute(),这意味着你没有把泛型放在正确的位置.


相反,在概念上,您希望getAttribute()本身不是一个泛型方法.BaseModel类本身应该是泛型的.更像是BaseModel<T>.getAttribute(),更像是BaseModel.getAttribute<T>(). 这意味着BaseModel不再是一个特定的类型.如果我们做了这个改变,将T extends BaseModelgetAttribute()移到类声明中,并将每一个BaseModel替换为BaseModel<T>来替换一个适当的T,看起来像这样:

class BaseModel<T extends BaseModel<T>> {
  protected getAttribute(): keyof T | null {
    return null;
  }
}

class Payment extends BaseModel<Payment> { }
class Item extends BaseModel<Item> { }

class Sale extends BaseModel<Sale> {
  value?: number;
  payment?: Payment;
  items?: Item[];

  protected override getAttribute(): keyof Sale | null {
    return 'payment';
  }
}

这样做很好,因为现在getAttribute()只能返回keyof T,用于包含类中定义的T. Payment就是Payment(Payment extends BaseModel<Payment>),Item就是ItemSale就是Sale.

所以这就解决了问题.但是:


这是一个curious类型的参数声明,不是吗? 我们得到了T extends BaseModel<T>,其中T受其自身函数的约束.另一种说法是,对于某个类型函数FTboundedF<T>.T是一个"F-bounded"通用.

如果你有一个形式为interface Foo<T extends Foo<T>> {⋯}type Bar<T extends Bar<T>> = ⋯;class Baz<T extends Baz<T>> {⋯}的F边界泛型,你可以经常(但不总是)用the polymorphic this type来避免泛型.this类型代表"当前"类型,它自动缩小子类型. 在BaseModel里面,thisBaseModel的一个子类型.但是在Payment里面,thisPayment的一个子类型. 本质上thisimplicitly一个F—有界泛型. 这建议我们做出以下改变:

class BaseModel {
  protected getAttribute(): keyof this | null {
    return null;
  }
}

class Payment extends BaseModel { }
class Item extends BaseModel { }

class Sale extends BaseModel {
  value?: number;
  payment?: Payment;
  items?: Item[];

  protected override getAttribute(): keyof Sale | null {
    return 'payment';
  }
}

显式泛型消失了,getAttribute()返回keyof this | null. 幸运的是,所有的编译都没有错误. 这可能会也可能不会最终适用于您的用例.对于T extends BaseModel<T>,你总是可以决定何时停止继承泛型,而this总是this,并且随着继承变得越来越窄.使用keyof this很容易,但如果你试图返回this,你会发现它有点棘手. 我不打算离题到什么时候以及如何为你工作.我只是说:try 它是合理的,记住如果你必须的话,你可以回到显式的F—有界泛型.

Playground link to code

Typescript相关问答推荐

如何将http上下文附加到angular中翻译模块发送的请求

当类型断言函数返回假时,TypScript正在推断从不类型

带有联合参数的静态大小数组

angular 17独立使用多个组件(例如两个组件)

泛型函数即使没有提供类型

对数组或REST参数中的多个函数的S参数进行类型判断

Angular NgModel不更新Typescript

Cypress页面对象模型模式.扩展Elements属性

使用Redux Saga操作通道对操作进行排序不起作用

垫表页脚角v15

在排版修饰器中推断方法响应类型

以Angular 执行其他组件的函数

从子类集合实例化类,同时保持对Typescript中静态成员的访问

有没有一种方法可以确保类型的成员实际存在于TypeScript中的对象中?

(TypeScript)TypeError:无法读取未定义的属性(正在读取映射)"

转换器不需要的类型交集

类型';只读<;部分<;字符串|记录<;字符串、字符串|未定义&>';上不存在TS 2339错误属性.属性&q;x&q;不存在字符串

从联合提取可调用密钥时,未知类型没有调用签名

可以';t使用t正确地索引对象;联合;索引类型

使用 Next.js 路由重定向到原始 URL 不起作用