如何在 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 中创建严格的“A 或 B”类型? [复制]