在TypeScript中,如何创建具有相同类型的子级和默认类型的可扩展泛型类型?

时间:2019-06-06 18:53:08

标签: typescript typescript-generics

起初,我有一个这样的类型:

type Foo = {
    bar: string;
    children: Foo[];
};

现在,我想允许扩展Foo类型,以便用户可以添加属性,但仍至少提供barchildren。我认为我可以轻松做到这一点:

interface Foo<T extends Foo<T>> {
    bar: string;
    children: T[];
}

但是后来我试图声明这种类型...而我不能: requires 1 type arg

这是一个无限循环...

因此,我然后尝试使用默认的泛型类型参数,但是再次,我无法... circular default

我要放屁吗?还是不可行?

我试图传达:

  • 类型Foo是可扩展的
  • 类型Foo具有与类型本身相同类型的children属性
  • 完全类型安全(因此,any在任何地方都没有-双关语意味)

3 个答案:

答案 0 :(得分:1)

取决于您想要扩展Foo类型的方式。如果要临时添加和访问属性(在代码中的任何时候),Foo的界面中必须具有“字符串索引签名”:

type Foo = {
    bar: string;
    children: Foo[];
    [key: string]: any; };

let fooInstance: Foo; 
fooInstance.bar = "toto"; 
fooInstance.newProperty = 24;

Vscode图片:

enter image description here

您还可以通过继承使用其他接口扩展Foo。

interface FooExtended extends Foo {
    age: number;
    street: string;
}

let extendedFoo : FooExtended;
extendedFoo.age = 22;
extendedFoo.bar = "popo";

您可以覆盖该接口,以将children数组与新接口进行匹配:

interface FooExtended extends Foo {
    age: number;
    street: string;
    children: FooExtended[];
}

答案 1 :(得分:1)

您可以添加空接口JustFoo来避免无限循环:

interface Foo<T> {
    bar: string;
    children: T[];
}

interface JustFoo extends Foo<JustFoo> {
}

interface Bar extends Foo<Bar> {
    barProp: number;
}

const bar: Bar = {
    barProp: 123,
    bar: 'aaa',
    children: [{ barProp: 123, bar: 'aaa', children: []}]
}

const foo: JustFoo = {
    bar: 'aaa',
    children: [{  bar: 'aaa', children: []}]
}

答案 2 :(得分:0)

好的, 感谢Lombas和aquz为我指出正确的方向。像以前一样,我试图简化我的问题以便在这里共享,而这样做却过于简化了。

我更准确的问题是以下结构:

export type NavigationEntry = NavigationEntryWithChildren | NavigationEntryWithoutChildren;

interface NavigationEntryBase {
    display: string;
    exact?: boolean;
}
interface NavigationEntryWithChildren extends NavigationEntryBase {
    children: NavigationEntry[];
}

interface NavigationEntryWithoutChildren extends NavigationEntryBase {
    routerLinkInfo: any[] | string;
}

顾名思义,这是关于创建导航结构的。在任何级别上,您都有一个URL(routerLinkInfo,或者您有孩子。因此,我不得不使用 union 类型,而不仅仅是接口。

尽管我已经全职从事TypeScript三年了,但我的头脑有时还是陷在更传统的面向对象的类型系统(如C#)中,而忘记了TypeScript会使其正常工作。

>

因此,我没有摆弄NavigationEntry并使其直接工作,而是创建了两个额外的类型:

interface CustomNavigationEntryWithChildren<T> extends NavigationEntryWithChildren {
    children: CustomNavigationEntry<T>[];
}

export type CustomNavigationEntry<T> =
    | (CustomNavigationEntryWithChildren<T> & T)
    | (NavigationEntryWithoutChildren & T);

使用这种方法,只需要NavigationEntry的所有现有代码仍然可以使用。想要在其导航节点上添加额外属性的较新项目可以做到这一点:

type Foo = CustomNavigationEntry<{foo: boolean}>;

以上内容将要求所有节点都具有foo布尔值。

希望这有助于下一个人(即使只是我自己;))