Vedere microsoft/Dattiloscritto#41164 per una canonica risposta a questa domanda.
Dattiloscritto non consente riferimenti circolari in generico interfacce e generico classi, dal momento che le interfacce e le istanze di classe sono staticamente nota proprietà/membri/metodo tastos e quindi qualsiasi circolarità accade in "cassaforte" luoghi come la proprietà values o parametri di un metodo o tipo di ritorno.
interface Interface<T> { val: T }
type X = Interface<X> // okay
class Class<T> { method(arg: T): void { } }
type Y = Class<Y> // okay
Ma per generico tipo alias non c'è alcuna garanzia. Tipo alias può avere qualsiasi struttura che qualsiasi tipo anonimo, in modo che il potenziale di una circolarità non è vincolata a ricorsiva di struttura ad albero di oggetti:
type Safe<T> = { val: T };
type Unsafe<T> = T | { val: string };
Quando il compilatore crea un'istanza di un tipo generico, si posticipa la sua valutazione; non tentare subito completamente calcolare la risultante tipo. Tutti si vede è la forma:
type WouldBeSafe = Safe<WouldBeSafe>;
type WouldBeUnsafe = Unsafe<WouldBeUnsafe>;
Sia di quelli che lo stesso aspetto per il compilatore... type X = SomeGenericTypeAlias<X>
. E non può "vedere" che WouldBeSafe
non sarebbe male:
//type WouldBeSafe = { val: WouldBeSafe }; // would be okay
mentre WouldBeUnsafe
sarebbe un problema:
//type WouldBeUnsafe = WouldBeUnsafe | { val: string }; // would be error
Poiché non può vedere la differenza, e perché almeno alcuni usi sarebbe illegalmente circolare, solo che li proibisce tutti.
Quindi, cosa si può fare? Questo è uno di quei casi in cui suggerirei interface
invece di type
quando è possibile. Puoi riscrivere il tuo record
tipo (cambiando di MyRecord
per la convenzione di denominazione motivi) come interface
e tutto funzionerà:
interface MyRecord<T> { val: T };
type B = MyRecord<B>; // okay
Si può anche riscrivere il tuo func
tipo (cambiando di Func
per la convenzione di denominazione motivi, ancora una interface
cambiando il tipo di funzione di espressione di sintassi in un call firma sintassi:
interface Func<T> { (arg: T): void }
type C = Func<C>; // okay
Ovviamente, ci sono situazioni in cui non si può fare direttamente, come il built-in Record
tipo di utilità:
type Darn = Record<string, Darn>; // error
e non è possibile riscrivere la mapped tipo Record
come interface
. E, infatti, sarebbe pericoloso per cercare di rendere le chiavi circolare, come type NoGood = Record<NoGood, string>
. Se si desidera solo per fare Record<string, T>
per generico T
si può riscrivere che come un interface
:
interface Dictionary<T> extends Record<string, T> { };
type Works = Dictionary<Works>;
Quindi c'è molto spesso un modo per utilizzare un interface
invece di type
per consentire di esprimere "sicuro" ricorsiva tipi.
Giochi link al codice