如何在TypeScript中创建循环引用类型?

sam*_*mvv 34 types circular-reference typescript

我有以下代码:

type Document = number | string | Array<Document>;
Run Code Online (Sandbox Code Playgroud)

TypeScript抱怨以下错误:

test.ts(7,6): error TS2456: Type alias 'Document' circularly references itself.
Run Code Online (Sandbox Code Playgroud)

显然不允许循环引用.但是,我仍然需要这种结构.这将是一个解决方法?

Kri*_*amp 31

TypeScript的创建者解释了如何在这里创建递归类型:https://github.com/Microsoft/TypeScript/issues/3496#issuecomment-128553540

循环引用的解决方法是使用extends Array.在您的情况下,这将导致此解决方案:


type Document = number | string | DocumentArray;

interface DocumentArray extends Array<Document> { }
Run Code Online (Sandbox Code Playgroud)


Dan*_*ber 17

我们已经有了很好的答案,但我认为我们可以从一开始就接近你想要的东西:

你可以尝试这样的事情:

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

与NPE的答案相比,您不需要围绕字符串和数字的包装器对象.

如果您希望单个数字或字符串成为有效文档(这不是您提出的问题,但NPE的答案意味着什么),您可以尝试这样做:

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

更新:

使用具有索引签名而不是数组的接口具有丢失类型信息的缺点.Typescript不允许您调用find,map或forEach等数组方法.例:

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

这可以通过更改DocumentArray的定义来解决:

interface DocumentArray extends Array<ScalarDocument | DocumentArray> {}
Run Code Online (Sandbox Code Playgroud)


NPE*_*NPE 13

这是一种方法:

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

let doc1: Doc = { val: 42 };
let doc2: Doc = { val: "the answer" };
let doc3: Doc = { val: [doc1, doc2] };
Run Code Online (Sandbox Code Playgroud)

引用自身的类型称为"递归类型",将在语言规范的第3.11.8节中讨论.以下摘录解释了为什么您的尝试无法编译:

类和接口可以在其内部结构中引用自身......

您的原始示例既不使用类也不使用接口; 它使用类型别名.


Her*_*ine 11

从 Typescript 4 开始,循环类型对于很多事情来说都是固定的,但对于 Record 来说却不是(这是设计使然的)。如果您遇到此问题,可以按照以下方法进行操作。

// This will fire a TS2456 error: Type alias "Tree" circularly reference itself
type Tree = Record<string, Tree | string>;
Run Code Online (Sandbox Code Playgroud)
// No error
type Tree = {
    [key: string]: Tree | string;
};
Run Code Online (Sandbox Code Playgroud)

参考:https ://github.com/microsoft/TypeScript/pull/33050#issuecomment-543365074