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

Posted

技术标签:

【中文标题】如何在 TypeScript 中创建循环引用类型?【英文标题】:How to create a circularly referenced type in TypeScript? 【发布时间】:2016-08-26 06:34:32 【问题描述】:

我有以下代码:

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

TypeScript 报错如下:

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

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

【问题讨论】:

显然,允许循环类型引用:***.com/questions/24444436/… 我很困惑为什么这里的联合类型被包裹在方括号中。可能不是故意的。见我的question here。 @bluenote10 你是对的,现在修复它。 TypeScript 3.7 中将提供对递归类型的原生支持,请参阅:github.com/microsoft/TypeScript/pull/33050 嗯,在 TS 4.3.5 中似乎仍然不起作用 【参考方案1】:

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

【讨论】:

太好了,所以我不是唯一一个在我的代码中这样做的疯子! :)【参考方案2】:

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

你可以试试这样的:

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> 

【讨论】:

您还可以将某些索引固定为特定类型,如下所示: interface DocumentArray 0: number; [索引:数字]:标量文档 |文档数组; 【参考方案3】:

这是一种方法:

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


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

引用自身的类型称为“递归类型”,并在语言规范的section 3.11.8 中进行了讨论。以下摘录解释了您的尝试无法编译的原因:

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

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

【讨论】:

感谢您的解释!这个答案绝对值得更高。【参考方案4】:

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

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

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

【讨论】:

确实,我想到了这一点,但不幸的是,我需要它具有无限的深度。无论如何感谢您的回答。【参考方案5】:

type Circular = 
    infinity: Map<string, Circular>


type Store = Circular['infinity']

declare var map:Store;

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

【讨论】:

【参考方案6】:

从 Typescript 4 开始,循环类型被固定为一堆东西,但不是为记录(这是设计使然)。 如果遇到此问题,请按照以下方法操作。

// 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

【讨论】:

以上是关于如何在 TypeScript 中创建循环引用类型?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 TypeScript 中创建自定义类型

是否可以在 TypeScript 中创建严格的“A 或 B”类型? [复制]

如何在 oracle 中创建具有对象数据类型的列

我可以在 TypeScript 中创建一个对每个键值对都是类型安全的键值字典吗?

如何在 TypeScript 中创建本地模块

如何在 JavaScript/TypeScript 中创建具有非唯一键的地图?