如何用Typescript描述原型继承?

3
以下代码为有效的Javascript。TypeScript并不能理解B中的`this.a`引用并会提示错误。是否有一些类型技巧可以应用于B,以向TypeScript解释运行时情况,并获得类型安全性的好处(例如,如果尝试访问`this.c`则报错)?

const A = {
  a: 1,
  bar() {
    this.a = 10;
  },
};

const B = {
  b: 2,
  f() {
    console.log(this.a + this.b);
  },
  bar() {
    super.bar();
    console.log(this.a);
  },
};

Reflect.setPrototypeOf(B, A);
B.f(); // 3
B.bar(); // 10


你能把它们变成类吗?我的意思是,在你的例子中,const A 上的 bar 方法不是类型安全的,thisany - Adam Jenkins
在我的 IDE 中,bar() 函数内的 const A 中的 this 被编译器推断为 { a: number; bar(): void; },而不是 any - jcalz
@jcalz - 不能说我深入研究了,我只是打开了一个新的 StackBlitz TypeScript 项目,它显示为 any - Adam Jenkins
1个回答

3

我认为唯一能让这样的东西工作(而不必在每个地方都明确地编写冗余的类型注释信息)的方法是,如果你将B的赋值和设置其原型的操作重构为单个函数调用,就像这样:

function setPrototype<T extends object, U extends object>(
  object: T & ThisType<T & U>,
  proto: U
) {
  Reflect.setPrototypeOf(object, proto);
  return object as T & U;
}

如果您调用setPrototype({...}, proto),其中{...}是某个通用类型T的对象文字,而proto是另一个通用类型U的对象:编译器将设置对象文字的原型为proto并返回它。
在这个调用中,你会看到对象字面量将会获得一个上下文类型的this,它是TU交集。这是通过神奇的ThisType<T>实用程序类型实现的。当我说“神奇”时,我的意思是,与大多数其他实用程序类型不同,你无法自己定义等效的类型。编译器确实赋予了ThisType<T>特殊的上下文能力;请参见microsoft/TypeScript#14141,了解其工作原理的描述。
最后,返回的对象类型也将具有T & U交集类型,因此您可以访问其自身属性和从原型继承的属性。

让我们试一下:

const A = {
  a: 1,
  bar() {
    this.a = 10;
  },
};

const B = setPrototype({
  b: 2,
  f() {
    console.log(this.a + this.b);
  },
  bar() {
    super.bar();
    console.log(this.a);
  },
}, A);


B.f(); // 3
B.bar(); // 10

看起来不错!现在没有错误了,编译器会推断出f()实现中的this.athis.b的上下文类型。


存在一些注意事项和边缘情况。交叉类型T & U可能不完全准确,但这是我可以轻松表示的最接近的方式。

更大的注意事项在于似乎没有一种类型安全的方法来处理super。如果没有像microsoft/TypeScript#42327中所要求的super参数注释之类的东西,就没有办法手动处理。即使您可以手动处理,它仍然无法让您获得ThisType<T>的自动上下文类型化...您需要一个新的神奇类型别名,例如SuperType<T>。因此,目前来看,这似乎超出了TypeScript的能力范围。

个人建议放弃这种手动原型操作,转而使用基于class的继承,因为TypeScript已经为支持ES2015+继承做了更多的工作,而对于支持ES5-,则没有进行太多的支持。但您可能有您采用这种方式的原因;希望上述代码能让您更接近实现有用的类型安全性。


代码的游乐场链接


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接