看到 microsoft/稿#41164 为一个典型的回答这个问题。
稿 并 允许圆引用 通用的 接口 和 通用 课程,由于接口和类实例有静态已知财产/构件/方法 的关键s因此,任何圆发生在"安全"的地方,如财产 价值或方法的参数或者返回的类型。
interface Interface<T> { val: T }
type X = Interface<X> // okay
class Class<T> { method(arg: T): void { } }
type Y = Class<Y> // okay
但是,对通用 类型的别名 ,没有这样的保证。 类别名可能有任何结构的任何匿名的类型可以拥有,因此,潜在的循环是没有约束递归的树类物体:
type Safe<T> = { val: T };
type Unsafe<T> = T | { val: string };
当编译器实例一般类型,它 将推迟 它的评价;它不会立刻尝试完全计算的所得类型。 所有它认为是形式:
type WouldBeSafe = Safe<WouldBeSafe>;
type WouldBeUnsafe = Unsafe<WouldBeUnsafe>;
这些看起来相同的编译器... type X = SomeGenericTypeAlias<X>
. 它不能"看见", WouldBeSafe
会好起来的:
//type WouldBeSafe = { val: WouldBeSafe }; // would be okay
同时 WouldBeUnsafe
将会是一个问题:
//type WouldBeUnsafe = WouldBeUnsafe | { val: string }; // would be error
因为它不能看到差别,并因为至少某些惯例将是非法的圆形的,它只是禁止所有的人。
那么,你能做些什么? 这是一个这些情况下,我建议使用 interface
而不是的 type
当你可以。 你可以重写你的 record
类型(改变它 MyRecord
命名《公约》的原因)为 interface
一切都会的工作:
interface MyRecord<T> { val: T };
type B = MyRecord<B>; // okay
你甚至可以重写你的 func
类型(改变它 Func
命名《公约》的原因再次)作为一个 interface
通过改变的 功能的类型表达 的语法成一个 呼吁的签名语法:
interface Func<T> { (arg: T): void }
type C = Func<C>; // okay
当然在有些情况下你不能这么直接,例如内置 Record
实用新型:
type Darn = Record<string, Darn>; // error
你不能改写 映类型 Record
作为一个 interface
. 确实,这将是不安全的尝试使键圆的,就像 type NoGood = Record<NoGood, string>
. 如果你只想要做的 Record<string, T>
对于一般 T
你 可以 改写 , 作为一个 interface
:
interface Dictionary<T> extends Record<string, T> { };
type Works = Dictionary<Works>;
因此,有相当经常的方式使用 interface
而不是的 type
为让你表达"安全"递归类型。
游乐场链接,以代码