如何在 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.a
和 this.b
的上下文类型
有一些警告和边缘情况。交集类型 T & U
可能不完全准确,但它与我可以轻松表示的一样接近。
这里更大的警告是似乎没有办法以类型安全的方式处理 super
。如果没有 super
参数注释之类的东西(如 microsoft/TypeScript#42327 中所要求的),则无法手动完成。即使您可以手动完成它,它仍然不会让您进入 ThisType<T>
的自动上下文输入...您还需要一个新的神奇类型别名,例如 SuperType<T>
。所以就目前而言,这似乎超出了 TypeScript 的能力。
就我个人而言,我建议放弃这种手动处理原型的方式,转而使用基于 class
的继承,因为在 TypeScript 中投入的工作比以往更多,以支持 ES2015+ 继承支持 ES5-。但是您大概有这样做的理由;希望上面的代码能让你更接近有用的类型安全。
下面的代码有效 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.a
和 this.b
的上下文类型
有一些警告和边缘情况。交集类型 T & U
可能不完全准确,但它与我可以轻松表示的一样接近。
这里更大的警告是似乎没有办法以类型安全的方式处理 super
。如果没有 super
参数注释之类的东西(如 microsoft/TypeScript#42327 中所要求的),则无法手动完成。即使您可以手动完成它,它仍然不会让您进入 ThisType<T>
的自动上下文输入...您还需要一个新的神奇类型别名,例如 SuperType<T>
。所以就目前而言,这似乎超出了 TypeScript 的能力。
就我个人而言,我建议放弃这种手动处理原型的方式,转而使用基于 class
的继承,因为在 TypeScript 中投入的工作比以往更多,以支持 ES2015+ 继承支持 ES5-。但是您大概有这样做的理由;希望上面的代码能让你更接近有用的类型安全。