如何在 Typescript 中描述原型继承?

How to describe prototypal inheritance in Typescript?

下面的代码有效 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

我认为让这样的事情起作用的唯一方法(不必在各处显式写出冗余类型注释信息)是重构 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),其中 {...}generic 类型 T 的某个对象文字,而 proto 是其他泛型对象类型 U:编译器会将对象字面量的原型设置为 proto 和 return。

在此调用中,您将看到对象文字将获得一个 contextual type for this which is an intersection of both T and U. This happens via the magic ThisType<T> utility type. When I say "magic" here, I mean that, unlike most other utility types, you could not define an equivalent type yourself. The compiler literally gives special contextual powers to ThisType<T>; see microsoft/TypeScript#14141 来描述其工作原理。

最后,returned 对象类型也将具有 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。如果没有 super 参数注释之类的东西(如 microsoft/TypeScript#42327 中所要求的),则无法手动完成。即使您可以手动完成它,它仍然不会让您进入 ThisType<T> 的自动上下文输入...您还需要一个新的神奇类型别名,例如 SuperType<T>。所以就目前而言,这似乎超出了 TypeScript 的能力。

就我个人而言,我建议放弃这种手动处理原型的方式,转而使用基于 class 的继承,因为在 TypeScript 中投入的工作比以往更多,以支持 ES2015+ 继承支持 ES5-。但是您大概有这样做的理由;希望上面的代码能让你更接近有用的类型安全。


Playground link to code