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

Using recursive type aliases gives circular reference error

根据 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,这确实不应该。这里有什么问题吗?

这里是link TypeScript playground on v4.1.3

请注意此代码来自 graphql 我们目前正在进行 migrating Flow to TypeScript。 这是 Flow equivalent.

我发现的解决方法是:

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

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

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

The specific change made by this PR is that type arguments are permitted to make circular references in aliased types [roughly, in type aliases] of the following kinds:

  • Instantiations of generic class and interface types (for example Array<Foo>).
  • Array types (for example Foo[]).
  • Tuple types (for example [string, Foo?]).

所以只有 Bar 是通用的 class[= 时才支持类似 type Foo = Bar<Foo> 的内容17=]。不支持 Bar 本身是 type 别名。

请参阅 a comment on the same pull request that explains this. See also microsoft/TypeScript#35017,一个开放的功能请求以解除此限制。


那么,我能想到的最简单的解决方法就是尽可能将每个 type 别名更改为 interface。在您的示例代码中,只有 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