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

如何在TypeScript中创建循环引用类型?,types,typescript,circular-reference,Types,Typescript,Circular Reference,我有以下代码: type Document = number | string | Array<Document>; 显然,不允许循环引用。但是,我仍然需要这种结构。解决方法是什么?这里有一种方法: class Doc { val: number | string | Doc[]; } let doc1: Doc = { val: 42 }; let doc2: Doc = { val: "the answer" }; let doc3: Doc = { val: [doc1

我有以下代码:

type Document = number | string | Array<Document>;

显然,不允许循环引用。但是,我仍然需要这种结构。解决方法是什么?

这里有一种方法:

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

let doc1: Doc = { val: 42 };
let doc2: Doc = { val: "the answer" };
let doc3: Doc = { val: [doc1, doc2] };
引用自身的类型称为“递归类型”,在语言规范的第1部分中讨论。以下摘录解释了您的尝试无法编译的原因:

类和接口可以在其内部结构中引用它们自己


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

根据NPE的说法,类型不能递归指向自己,您可以将此类型展开到您认为足够的深度级别,例如:

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

不太漂亮,但不需要使用属性值的接口或类。

我们已经有了很好的答案,但我认为我们可以更接近您首先想要的:

您可以尝试以下方法:

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 }]];
与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 ];
更新:

使用带有索引签名的接口而不是数组具有丢失类型信息的缺点。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);
}
type ScalarDocument=number |字符串;
接口文档{
[索引:编号]:ScalarDocument | DocumentArray;
}
类型文件=比例文件|文件阵列;
const doc1:Document=1;
const doc2:Document=“一”;
constdoc3:Document=[doc1,doc2];
const doc=Math.random()<0.5?doc1:(Math.random()<0.5?doc2:doc3);
如果(单据类型==“编号”){
doc-1;
}else if(单据类型==“字符串”){
toUpperCase()博士;
}否则{
//失败,原因是“类型“DocumentArray”上不存在属性“map”
doc.map(d=>d);
}
这可以通过更改文档安排的定义来解决:

interface DocumentArray extends Array<ScalarDocument | DocumentArray> {}
interface DocumentArray扩展数组{}

TypeScript的创建者解释了如何创建递归类型

循环引用的解决方法是使用
扩展数组
。在您的情况下,这将导致此解决方案:

type Document = number | string | DocumentArray;

interface DocumentArray extends Array<Document> { }
type Document=number | string | DocumentArray;
接口文档阵列扩展数组{}
更新(TypeScript 3.7)

从TypeScript 3.7开始,将允许使用递归类型别名,不再需要解决方法。请参阅:


圆形类型={
无限:地图
}
类型存储=循环['infinity']
声明var映射:Store;
const foo=map.get('sd')//循环|未定义

的确,我想到了这一点,但不幸的是,我需要它具有无限的深度。不管怎样,谢谢你的回答。显然,循环类型引用是允许的:我很困惑为什么这里的联合类型是用方括号括起来的。也许这不是故意的。请参阅我的。@bluenote10您说得对,现在正在修复它。TypeScript 3.7中提供了对递归类型的本机支持,请参阅:酷!当问题出现时,我会更新它。您也可以将某些索引固定到特定类型,如:interface DocumentArray{0:number;[index:number]:ScalarDocument | DocumentArray;}谢谢您的解释!这个答案绝对应该更高。太好了,所以我不是代码中唯一一个这样做的疯子!)
type Document = number | string | DocumentArray;

interface DocumentArray extends Array<Document> { }