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

How to create a circularly referenced type in TypeScript?

我有以下代码:

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

TypeScript 报错如下:

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

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

这是一种方法:

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

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

引用自身的类型称为 "recursive types" 并在语言规范的 section 3.11.8 中进行了讨论。以下摘录解释了为什么您的尝试无法编译:

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

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

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

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

不漂亮,但不需要接口或 class 具有 属性 值。

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

您可以尝试这样的操作:

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);
}

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

interface DocumentArray extends Array<ScalarDocument | DocumentArray> {}

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

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

type Document = number | string | DocumentArray;

interface DocumentArray extends Array<Document> { }

更新 (TypeScript 3.7)

从 TypeScript 3.7 开始,将允许使用递归类型别名,并且不再需要解决方法。参见:https://github.com/microsoft/TypeScript/pull/33050


type Circular = {
    infinity: Map<string, Circular>
}

type Store = Circular['infinity']

declare var map:Store;

const foo = map.get('sd') // Circular | undefined

从 Typescript 4 开始,循环类型对于一堆东西是固定的,但对于 Record 则不是(这是设计使然)。 如果您遇到此问题,可以按照以下方法进行操作。

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

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