使用递归类型别名会产生循环引用错误
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
会抛出一个循环引用错误,并且将鼠标悬停在 IntrospectionListTypeRef
或 IntrospectionNonNullTypeRef
上会显示 T extends any = any
,这确实不应该。这里有什么问题吗?
这里是link TypeScript playground on v4.1.3
请注意此代码来自 graphql
我们目前正在进行 migrating Flow to TypeScript。
这是 Flow equivalent.
我发现的解决方法是:
- 将
any
传递给IntrospectionListTypeRef
。但这确实不是一个理想的解决方案。参见 GitHub diff
- 删除
IntrospectionListTypeRef
和 IntrospectionNonNullTypeRef
的泛型类型,然后它会起作用,但问题是我们尝试以这种方式移植更多类型,这意味着我们正在复制类型。
还提出了一个问题,请参阅 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'];
};
现在没有错误了,万岁!
根据 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
会抛出一个循环引用错误,并且将鼠标悬停在 IntrospectionListTypeRef
或 IntrospectionNonNullTypeRef
上会显示 T extends any = any
,这确实不应该。这里有什么问题吗?
这里是link TypeScript playground on v4.1.3
请注意此代码来自 graphql
我们目前正在进行 migrating Flow to TypeScript。
这是 Flow equivalent.
我发现的解决方法是:
- 将
any
传递给IntrospectionListTypeRef
。但这确实不是一个理想的解决方案。参见 GitHub diff - 删除
IntrospectionListTypeRef
和IntrospectionNonNullTypeRef
的泛型类型,然后它会起作用,但问题是我们尝试以这种方式移植更多类型,这意味着我们正在复制类型。
还提出了一个问题,请参阅 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'];
};
现在没有错误了,万岁!