网站搜索

将 TypeScript 泛型作为其自身的参数使用,或将其指定为自身的默认参数


我有以下 ItemBase 接口以及多个从它继承的递归 xItem 接口:

interface ItemBase<Children extends ItemBase<Children>> {
  x: string;
  y: number;
  children: Children[];
}

interface FooItem extends ItemBase<FooItem> {
  foo: boolean;
}

interface BarItem extends ItemBase<BarItem> {
  bar: "b" | "a" | "r";
}

我现在想引用 ItemBase,并将其自身作为它自己的参数:

const transformItem = (item: ItemBase<ItemBase>) => { ... }
//                                    ^
// TS2314: Generic type 'ItemBase ' requires 1 type argument(s).

但是这行不通,因为我没有指定内部 ItemBase 的参数(显然也不可能一直无限制地指定这些参数)。

更好的方法是将其自身作为 ItemBase 的默认参数……我尝试了两种方法,但每种方法都会遇到各自的错误:

interface ItemBase<Children extends ItemBase<Children> = ItemBase<Children>> {
//                                                                ^
// TS2744: Type parameter defaults can only reference previously declared type parameters.
  x: string;
  y: number;
  children: Children[];
}
interface ItemBase<Children extends ItemBase<Children> = ItemBase> {
//                                                       ^
// TS2716: Type parameter 'Children' has a circular default.
  x: string;
  y: number;
  children: Children[];
}

一个明显的解决方法是创建一个单独的接口来表示我自引用的基类:

interface BaseItem extends ItemBase<BaseItem> {}
const transformItem = (item: BaseItem) => { ... }

但那更像是一种权宜之计,因为其他项目并不真正继承它。

我还有其他哪些选择?

答案#1

根据你的使用场景,你可能想使用多态的 this 类型。在 interface 或 class 声明中,你可以使用名为 this 的类型来引用“当前”的子类型。就好像 this 是一个“隐式”的泛型类型参数,其行为类似于递归约束(也叫“F-界多态”)在 interface Foo<T extends Foo<T>> 中的表现。它使你无需显式使用泛型:

interface ItemBase {
    x: string;
    y: number;
    children: this[];
}

interface FooItem extends ItemBase {
    foo: boolean;
}

interface BarItem extends ItemBase {
    bar: "b" | "a" | "r";
}

为了看到它的实际应用,以下是一些可能的用法:

const fooItem: FooItem = {
    x: "a",
    y: 1,
    foo: true,
    children: [{
        x: "b", y: 2, foo: true, children: []
    }]
}

function processItem(item: ItemBase) {
    console.log(item.x.toUpperCase());
    console.log(item.y.toFixed(2));
    item.children.forEach(i => processItem(i));
}

processItem(fooItem); // A, 1,00, B, 2.00

在某些情况下,多态的 this 很难使用,但由于问题中没有更具体的使用案例,我不确定你是否会遇到这些情况。

代码的 Playground 链接