Typescript - 泛型类型扩展自身

9

最近我遇到了类似这样的东西:

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

function foo <T extends Test<T>>(el: T): T {
  ...
}

我必须说我有点困惑这是什么,以及为什么需要这样的东西。我已经阅读了Typescript手册中的泛型部分,但没有找到类似的内容。
那个接口实现了什么,不能用以下内容实现吗?
interface Test<T>

有人能解释一下这个吗?

2个回答

13

没有实际的例子,我只能讲一些概念。在像Java这样没有多态 this类型的语言中,你需要这种语法。


这个概念是你想要一个泛型类型,它指向其包含类或接口的其他对象,这些对象与其具有相同的类型。让我们看一下你的Test接口:

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

这描述了一种类似于链表的结构,其中 Test<T>b 属性必须也是一个 Test<T>,因为 T 扩展了 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;

ChocolateTestVanillaTest都是Test的实现,但它们不能互换。 ChocolateTestb属性是ChocolateTest,而VanillaTestb属性是VanillaTest。 因此会发生以下错误,这是期望的:

choc.b = vani; // error!

现在你知道当你有一个ChocolateTest时,整个列表都充满了其他的ChocolateTest实例,而不必担心其他Test会出现:
choc.b.b.b.b.b.b.b.b.b // <-- still a ChocolateTest

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

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}}的某些实现。到目前为止,它看起来是相同的,但以下行为是不同的:
relaxedChoc.b = relaxedVani; // no error

这是被允许的,因为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!

这个代码几乎与原始的 Test<T extends Test<T>> 一样,但是没有可能令人费解的循环引用。所以,我建议使用多态的this,除非你有某些强制要求以另一种方式完成它。
既然你说你遇到了这个代码,我想知道它是否是在多态的this引入之前编写的代码,或者是由不知道多态的人编写的,或者是否存在我不知道的令人信服的理由。不确定。

希望这有意义并对你有所帮助。祝你好运!


多好的答案!需要花些时间仔细阅读它。 - bugs

2
public static foo<TType extends number | string, T extends Tree<TType>>(data: T[]): T[] {
    console.log(data[0].key);
    return
}


export interface Tree<T> {
    label?: string;
    data?: any;
    parent?: Tree<T>;
    parentId?: T;
    key?: T;
}

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