使用递归类型别名会产生循环引用错误

Posted

技术标签:

【中文标题】使用递归类型别名会产生循环引用错误【英文标题】:Using recursive type aliases gives circular reference error 【发布时间】:2021-04-17 23:43:32 【问题描述】:

根据TypeScript v3.7,递归类型别名可以使用。

export type IntrospectionType = 
  readonly kind: 'OBJECT';
;

export type IntrospectionListTypeRef<
  T extends IntrospectionTypeRef = IntrospectionTypeRef
> = 
  readonly kind: 'LIST';
  readonly ofType: T;
;

export type IntrospectionNonNullTypeRef<
  T extends IntrospectionTypeRef = IntrospectionTypeRef
> = 
  readonly kind: 'NON_NULL';
  readonly ofType: T;
;

export type IntrospectionTypeRef =
  | IntrospectionNamedTypeRef
  | IntrospectionListTypeRef
  | IntrospectionNonNullTypeRef<
      IntrospectionNamedTypeRef | IntrospectionListTypeRef
    >;

export type IntrospectionNamedTypeRef<
  T extends IntrospectionType = IntrospectionType
> = 
  readonly kind: T['kind'];
;
 

在这种情况下,IntrospectionTypeRef 会引发循环引用错误,并将鼠标悬停在IntrospectionListTypeRefIntrospectionNonNullTypeRef 上会显示T extends any = any,它确实不应该这样做。这里有什么问题吗?

这是TypeScript playground on v4.1.3的链接

请注意,此代码来自graphql,我们目前正在处理migrating Flow to TypeScript。 这是Flow equivalent。

我发现的解决方法是:

    any 传递给IntrospectionListTypeRef。但这确实不是一个理想的解决方案。见GitHub diff 删除 IntrospectionListTypeRefIntrospectionNonNullTypeRef 的泛型类型然后它会起作用,但问题是我们正在尝试移植更多类型,这意味着我们正在复制类型。

还提交了一个问题,请参阅TypeScript #42308

【问题讨论】:

【参考方案1】:

TypeScript 不允许任意循环类型定义。如果我们查看microsoft/TypeScript#33050,它引入了对 TypeScript 3.7 循环类型引用的添加支持的拉取请求,它说:

此 PR 所做的具体更改是允许类型参数在以下类型的 aliased 类型中进行循环引用 [大致,在类型别名中]:

泛型类和接口类型的实例化(例如Array&lt;Foo&gt;)。 数组类型(例如Foo[])。 元组类型(例如[string, Foo?])。

所以只有当Bar 是一个通用的classinterface 时,它才支持像type Foo = Bar&lt;Foo&gt; 这样的东西。不支持 Bar 本身是 type 别名。

请参阅 a comment on the same pull request 来解释这一点。另请参阅 microsoft/TypeScript#35017,这是一个开放功能请求,旨在解除此限制。


然后,我能想到的最简单的解决方法是尽可能将您的每个 type 别名更改为 interfaces。在您的示例代码中,只有 IntrospectionTypeRef 本身需要保留 type 别名,因为它是联合类型。其他的都可以改:

export interface IntrospectionType 
  readonly kind: 'OBJECT';
;

export interface IntrospectionListTypeRef<
  T extends IntrospectionTypeRef = IntrospectionTypeRef
  > 
  readonly kind: 'LIST';
  readonly ofType: T;
;

export interface IntrospectionNonNullTypeRef<
  T extends IntrospectionTypeRef = IntrospectionTypeRef
  > 
  readonly kind: 'NON_NULL';
  readonly ofType: T;
;

export type IntrospectionTypeRef =
  | IntrospectionNamedTypeRef
  | IntrospectionListTypeRef
  | IntrospectionNonNullTypeRef<
    IntrospectionNamedTypeRef | IntrospectionListTypeRef
  >;

export interface IntrospectionNamedTypeRef<
  T extends IntrospectionType = IntrospectionType
  > 
  readonly kind: T['kind'];
;

export interface IntrospectionType 
  readonly kind: 'OBJECT';
;

export interface IntrospectionListTypeRef<
  T extends IntrospectionTypeRef = IntrospectionTypeRef
  > 
  readonly kind: 'LIST';
  readonly ofType: T;
;

export interface IntrospectionNonNullTypeRef<
  T extends IntrospectionTypeRef = IntrospectionTypeRef
  > 
  readonly kind: 'NON_NULL';
  readonly ofType: T;
;

export type IntrospectionTypeRef =
  | IntrospectionNamedTypeRef
  | IntrospectionListTypeRef
  | IntrospectionNonNullTypeRef<
    IntrospectionNamedTypeRef | IntrospectionListTypeRef
  >;

export interface IntrospectionNamedTypeRef<
  T extends IntrospectionType = IntrospectionType
  > 
  readonly kind: T['kind'];
;

现在没有错误了,万岁!

Playground link to code

【讨论】:

以上是关于使用递归类型别名会产生循环引用错误的主要内容,如果未能解决你的问题,请参考以下文章

引用循环:ON DELETE CASCADE?

为啥我的递归会产生意外错误?以及如何在到达循环时修改递归?

maven循环引用的问题

函数高级类型名别名if-else 的使用包的使用for循环swich的使用数组的使用

使用weak strong dance 解决 block 循环引用

【OC语法】block的循环引用