打字稿-扩展自身的通用类型

bug*_*ugs 4 generics typescript

我最近遇到了一个看起来像这样的东西:

interface Test<T extends Test<T>> {
  a: number;
  b: T;
}

function foo <T extends Test<T>>(el: T): T {
  ...
}
Run Code Online (Sandbox Code Playgroud)

我必须说这到底是什么,以及为什么需要这样的东西有些困惑。我浏览过Typescript手册的“ 泛型”部分,但找不到类似的内容。

该接口实现了什么,而用以下类似的东西无法完成?

interface Test<T>
Run Code Online (Sandbox Code Playgroud)

谁能对此有所启发?

jca*_*alz 6

没有实际的例子,我只能概括地说。这类语法正是Java之类的语言所需要的,而Java没有多态this类型,我将在稍后介绍。


这个想法是您想要一个泛型类型,该泛型类型引用与其包含的类或接口相同类型的其他对象。让我们看看您的Test界面:

interface Test<T extends Test<T>> {
  a: number;
  b: T;
}
Run Code Online (Sandbox Code Playgroud)

这描述了一个类似链表的结构,其中a的b属性Test<T>也必须是a Test<T>,因为Textends Test<T>。但此外,它必须是与父对象相同的类型(的子类型)。这是两个实现的示例:

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;
Run Code Online (Sandbox Code Playgroud)

这两个ChocolateTestVanillaTest是的实现Test,但它们不是可以互换。a的b属性ChocolateTest是a ChocolateTest,而a的b属性VanillaTest是a VanillaTest。因此发生以下错误,这是所希望的:

choc.b = vani; // error!
Run Code Online (Sandbox Code Playgroud)

现在您知道有了时ChocolateTest,整个列表中充满了其他ChocolateTest实例,而无需担心其他实例的Test出现:

choc.b.b.b.b.b.b.b.b.b // <-- still a ChocolateTest
Run Code Online (Sandbox Code Playgroud)

将此行为与以下界面进行比较:

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;
Run Code Online (Sandbox Code Playgroud)

您可以看到RelaxedTest并不限制该b属性与父级具有相同的类型,仅限于实现RelaxedTest。到目前为止,它看起来相同,但是以下行为不同:

relaxedChoc.b = relaxedVani; // no error
Run Code Online (Sandbox Code Playgroud)

这是允许的,因为relaxedChoc.b是类型的RelaxedTest,这relaxedVani是兼容的。而choc.b为型Test<ChocolateTest>,这vani兼容的。


一个类型将另一个类型约束为与原始类型相同的能力很有用。实际上,它是如此有用,以至于TypeScript 为此目的具有一种称为多态的this东西。您可以使用this一种类型来表示“与包含的类/接口相同的类型”,并删除上面的通用内容:

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!
Run Code Online (Sandbox Code Playgroud)

这种行为几乎与原始行为相同,而Test<T extends Test<T>>没有可能使人弯腰的圆形性。所以,是的,this除非您有其他令人信服的理由,否则我建议您改用多态。

既然您说过您遇到了这段代码,我想知道它是来自引入多态之前的代码this,还是某个不了解它的人编写的代码,还是有一些我不知道的令人信服的原因。不确定。


希望这对您有所帮助。祝好运!