打字稿-扩展自身的通用类型
我最近遇到了一个看起来像这样的东西:
I recently came across something that looked like this:
interface Test<T extends Test<T>> {
a: number;
b: T;
}
function foo <T extends Test<T>>(el: T): T {
...
}
我不得不说我对这到底是什么以及为什么需要类似的东西感到困惑.我浏览过Typescript手册的泛型部分,但找不到任何类似的东西.
I have to say I am a bit perplexed as to what exactly this is, and why something like this would be required. I've been through the Generics section of the Typescript handbook but couldn't find anything similar.
该界面所能实现的功能无法通过以下方式完成?
What does that interface achieve that can't be done with something like the following?
interface Test<T>
任何人都可以对此有所了解吗?
Can anyone shed some light on this?
没有实际的示例,我只能概括地说.像Java这样没有多态的此
类型,我将在稍后介绍.
Without the actual example, I can only speak in generalities. That sort of syntax is what you need in a language like Java that doesn't have polymorphic this
types, which I'll get to shortly.
这个想法是,您想要一个泛型类型,该泛型类型将相同类型的其他对象引用为其包含的类或接口.让我们看看您的 Test
界面:
The idea is that you want a generic type that refers to other objects of the same type as its containing class or interface. Let's look at your Test
interface:
interface Test<T extends Test<T>> {
a: number;
b: T;
}
这描述了类似链表的结构,其中 Test< T>
的 b
属性也必须是 Test< T>
,因为 T
扩展了 Test< T>
.但此外,它必须与父对象相同类型(的子类型)相同.这是两个实现的示例:
This describes a linked-list-like structure where the b
property of a Test<T>
must also be a Test<T>
, since T
extends Test<T>
. But additionally, it must be (a subtype of) the same type as the parent object. Here's an example of two implementations:
interface ChocolateTest extends Test<ChocolateTest> {
flavor: "chocolate";
}
const choc = {a: 0, b: {a: 1, flavor: "chocolate"}, flavor: "chocolate"} as ChocolateTest;
choc.b.b = choc;
interface VanillaTest extends Test<VanillaTest> {
flavor: "vanilla";
}
const vani = {a: 0, b: {a: 1, flavor: "vanilla"}, flavor: "vanilla"} as VanillaTest;
vani.b.b = vani;
ChocolateTest
和 VanillaTest
都是 Test
的实现,但它们不可互换. ChocolateTest
的 b
属性是 ChocolateTest
,而 VanillaTest 的
b
属性code>是 VanillaTest
.因此会发生以下错误,这是所希望的:
Both ChocolateTest
and VanillaTest
are implementations of Test
, but they are not interchangable. The b
property of a ChocolateTest
is a ChocolateTest
, while the b
property of a VanillaTest
is a VanillaTest
. So the following error occurs, which is desirable:
choc.b = vani; // error!
现在,您知道拥有 ChocolateTest
时,整个列表充满了其他 ChocolateTest
实例,而无需担心出现其他 Test
:
Now you know when you have a ChocolateTest
that the entire list is full of other ChocolateTest
instances without worrying about some other Test
showing up:
choc.b.b.b.b.b.b.b.b.b // <-- still a ChocolateTest
将此行为与以下界面进行比较:
Compare this behavior to the following interface:
interface RelaxedTest {
a: number;
b: RelaxedTest;
}
interface RelaxedChocolateTest extends RelaxedTest {
flavor: "chocolate";
}
const relaxedChoc: RelaxedChocolateTest = choc;
interface RelaxedVanillaTest extends RelaxedTest {
flavor: "vanilla";
}
const relaxedVani: RelaxedVanillaTest = vani;
您可以看到 RelaxedTest
并没有将 b
属性限制为与父级相同的 类型,只是实现了 RelaxedTest
.到目前为止,它看起来相同,但是以下行为不同:
You can see that RelaxedTest
doesn't constrain the b
property to be the same type as the parent, just to some implementation of RelaxedTest
. So far, it looks the same, but the following behavior is different:
relaxedChoc.b = relaxedVani; // no error
这是允许的,因为 relaxedChoc.b
的类型为 RelaxedTest
,与 relaxedVani
兼容.而 choc.b
的类型为 Test< ChocolateTest>
,而 vani
与不不兼容.
This is allowed because relaxedChoc.b
is of type RelaxedTest
, which relaxedVani
is compatible with. Whereas choc.b
is of type Test<ChocolateTest>
, which vani
is not compatible with.
一种类型将另一种类型约束为与原始类型相同的能力是有用的.实际上,它非常有用,TypeScript具有称为 polymorphic的东西 this
仅用于此目的.您可以使用 this
作为类型来表示与包含的类/接口相同的类型",并删除上面的通用内容:
That ability of a type to constrain another type to be the same as the original type is useful. It's so useful, in fact, that TypeScript has something called polymorphic this
for just this purpose. You can use this
as a type to mean "the same type as the containing class/interface", and do away with the generic stuff above:
interface BetterTest {
a: number;
b: this; // <-- same as the implementing subtype
}
interface BetterChocolateTest extends BetterTest {
flavor: "chocolate";
}
const betterChoc: BetterChocolateTest = choc;
interface BetterVanillaTest extends BetterTest {
flavor: "vanilla";
}
const betterVani: BetterVanillaTest = vani;
betterChoc.b = betterVani; // error!
行为与原始 Test< T扩展Test< T>>
几乎相同,而无需可能会弯腰.所以,是的,除非您有其他令人信服的理由,否则我建议您改用多态的 this
.
This acts nearly the same as the original Test<T extends Test<T>>
without the possibly mind-bending circularity. So, yeah, I'd recommend using polymorphic this
instead, unless you have some compelling reason to do it the other way.
因为您说过您遇到了这段代码,所以我想知道它是来自引入多态的 this
之前的某个代码,还是某个不认识它的人编写的代码,或者是否存在某些代码?我不知道的令人信服的理由.不确定.
Since you said you came across this code, I wonder if it was some code from before the introduction of polymorphic this
, or by someone who didn't know about it, or if there is some compelling reason I don't know about. Not sure.
希望对您有所帮助.祝你好运!
Hope that makes sense and helps you. Good luck!