为什么打字稿不允许泛型中的循环引用?

rad*_*ish 5 types typescript

这里的示例中,类型在其定义中直接引用自身,但是当通过泛型抽象时,它完全失败。

type a = { val: a }; // <-- doesn't care about circular references!

type record<T> = { val: T };

type b = record<b>; // <-- doesn't work!

type func<T> = (arg: T) => void;

type c = func<c>; // <-- doesn't work!

type d = (arg: d) => void; // <-- works!?
Run Code Online (Sandbox Code Playgroud)

jca*_*alz 8

有关此问题的规范答案,请参阅microsoft/TypeScript#41164 。

TypeScript确实允许泛型 接口泛型中的循环引用,因为接口和类实例具有静态已知的属性/成员/方法,因此任何循环都发生在“安全”的地方,例如属性或方法参数或返回类型。

interface Interface<T> { val: T }
type X = Interface<X> // okay

class Class<T> { method(arg: T): void { } }
type Y = Class<Y> // okay
Run Code Online (Sandbox Code Playgroud)

但对于泛型类型别名则没有这样的保证。类型别名可以具有任何匿名类型可以具有的任何结构,因此潜在的循环不限于递归树状对象:

type Safe<T> = { val: T };
type Unsafe<T> = T | { val: string };
Run Code Online (Sandbox Code Playgroud)

当编译器实例化泛型类型时,它会推迟其计算;它不会立即尝试完全计算结果类型。它看到的只是形式:

type WouldBeSafe = Safe<WouldBeSafe>; 
type WouldBeUnsafe = Unsafe<WouldBeUnsafe>; 
Run Code Online (Sandbox Code Playgroud)

对于编译器来说,这两个看起来都是一样的type X = SomeGenericTypeAlias<X>...... 它无法“看到”那WouldBeSafe没关系:

//type WouldBeSafe = { val: WouldBeSafe }; // would be okay
Run Code Online (Sandbox Code Playgroud)

而这WouldBeUnsafe会是一个问题:

//type WouldBeUnsafe = WouldBeUnsafe | { val: string }; // would be error
Run Code Online (Sandbox Code Playgroud)

由于它看不到差异,并且因为至少某些用法会被非法循环,所以它只是禁止所有这些用法。


所以,你可以做什么?interface这是我建议使用而不是当你可以时使用的情况之一type。您可以将您的record类型重写MyRecord为 an interface,一切都会正常工作:

interface MyRecord<T> { val: T };
type B = MyRecord<B>; // okay
Run Code Online (Sandbox Code Playgroud)

您甚至可以通过将函数类型表达式语法更改为调用签名语法func来重写您的类型(Func再次出于命名约定原因将其更改为 an) :interface

interface Func<T> { (arg: T): void }
type C = Func<C>; // okay
Run Code Online (Sandbox Code Playgroud)

当然,有些情况下你不能直接这样做,例如内置Record实用程序类型

type Darn = Record<string, Darn>; // error
Run Code Online (Sandbox Code Playgroud)

并且您无法将映射类型 Record重写为interface. 事实上,尝试将密钥做成圆形是不安全的,例如type NoGood = Record<NoGood, string>Record<string, T>如果您只想为 generic做T,您可以将重写为interface

interface Dictionary<T> extends Record<string, T> { };
type Works = Dictionary<Works>;
Run Code Online (Sandbox Code Playgroud)

因此,通常有一种方法可以使用 aninterface而不是type来表达“安全”递归类型。

Playground 代码链接