How to create a circularly referenced type in Type

2019-02-07 22:10发布

I have the following code:

type Document = [number | string | Array<Document>]

TypeScript complains with the following error:

test.ts(7,6): error TS2456: Type alias 'Document' circularly references itself.

Clearly circular references are not allowed. However, I still need this kind of structure. What would be a workaround for this?

4条回答
淡お忘
2楼-- · 2019-02-07 22:48

The creator of TypeScript explains how to create recursive types here: https://github.com/Microsoft/TypeScript/issues/3496#issuecomment-128553540

The workaround for the circular reference is to use extends Array. In your case this would lead to this solution:


type Document = [number | string | DocumentArray]

interface DocumentArray extends Array<Document> { }
查看更多
做个烂人
3楼-- · 2019-02-07 22:49

We already have good answers, but I think we can get closer to what you wanted in the first place:

You may try something like this:

interface Document {
    [index: number]: number | string | Document;
}

// compiles
const doc1: Document = [1, "one", [2, "two", [3, "three"]]];

// fails with "Index signatures are incompatible" which probably is what you want
const doc2: Document = [1, "one", [2, "two", { "three": 3 }]];

Compared to NPE's answer, you don't need wrapper objects around strings and numbers.

If you want a single number or string to be a valid document (which is not what you asked, but what NPE's answer implies), you may try this:

type ScalarDocument = number | string;
interface DocumentArray {
    [index: number]: ScalarDocument | DocumentArray;
}
type Document = ScalarDocument | DocumentArray;

const doc1: Document = 1;
const doc2: Document = "one";
const doc3: Document = [ doc1, doc2 ];

Update:

Using an interface with index signature instead of an array has the disadvantage of losing type information. Typescript won't let you call array methods like find, map or forEach. Example:

type ScalarDocument = number | string;
interface DocumentArray {
    [index: number]: ScalarDocument | DocumentArray;
}
type Document = ScalarDocument | DocumentArray;

const doc1: Document = 1;
const doc2: Document = "one";
const doc3: Document = [ doc1, doc2 ];
const doc = Math.random() < 0.5 ? doc1 : (Math.random() < 0.5 ? doc2 : doc3);

if (typeof doc === "number") {
    doc - 1;
} else if (typeof doc === "string") {
    doc.toUpperCase();
} else {
    // fails with "Property 'map' does not exist on type 'DocumentArray'"
    doc.map(d => d);
}

This can be solved by changing the definition of DocumentArray:

interface DocumentArray extends Array<ScalarDocument | DocumentArray> {}
查看更多
我命由我不由天
4楼-- · 2019-02-07 22:55

Building on what NPE said, types cannot recursively point to themselves, you could unroll this type to whatever level of depth you considered sufficient, e.g.:

type Document = [number|string|[number|string|[number|string|[number|string]]]]

Not pretty, but removes the need for an interface or class with a property value.

查看更多
我想做一个坏孩纸
5楼-- · 2019-02-07 23:06

Here is one way to do it:

class Doc {
  val: number | string | Doc[];
}

let doc1: Doc = { val: 42 };
let doc2: Doc = { val: "the answer" };
let doc3: Doc = { val: [doc1, doc2] };

Types that reference themselves are known as "recursive types" and are discussed in section 3.11.8 of the language spec. The following excerpt explains why your attempt does not compile:

Classes and interfaces can reference themselves in their internal structure...

Your original example uses neither a class nor an interface; it uses a type alias.

查看更多
登录 后发表回答