Cm. microsoft/TypeScript#41164 aby uzyskać kanonicznego odpowiedzi na to pytanie.
Pisanie na klawiaturze dopuszcza odwołania cykliczne w uniwersalnych interfejsów i uniwersalnych klasach, ponieważ interfejsy i instancje klas mają statycznie znane klucze właściwości/elementów/metody, i dlatego każda cykliczność dzieje się w "bezpiecznych" miejscach, takich jak wartości właściwości lub ustawienia metody lub typ zwracanej wartości.
interface Interface<T> { val: T }
type X = Interface<X> // okay
class Class<T> { method(arg: T): void { } }
type Y = Class<Y> // okay
Ale dla wspólnych aliasów typów takiej gwarancji nie ma. Aliasy typów mogą mieć dowolną strukturę, która może mieć dowolny typ anonimowy, więc potencjalny cykliczność nie ogranicza się do рекурсивными treelike, jak:
type Safe<T> = { val: T };
type Unsafe<T> = T | { val: string };
Gdy kompilator tworzy instancję uniwersalnego rodzaju, on odkłada jego ocenę; nie raz próbuje całkowicie obliczyć wypadkowy typ. Wszystko, co widzi, to kształt:
type WouldBeSafe = Safe<WouldBeSafe>;
type WouldBeUnsafe = Unsafe<WouldBeUnsafe>;
Oba wyglądają tak samo dla kompilatora... type X = SomeGenericTypeAlias<X>
. On nie może "widzieć", że WouldBeSafe
wszystko byłoby w porządku:
//type WouldBeSafe = { val: WouldBeSafe }; // would be okay
aż WouldBeUnsafe
to byłby problem:
//type WouldBeUnsafe = WouldBeUnsafe | { val: string }; // would be error
Ponieważ on nie widzi różnicy i ponieważ, przynajmniej niektóre zastosowania byłyby nielegalne, zakazane, to po prostu zabrania je wszystkie.
Więc, co można zrobić? Jest to jeden z tych przypadków, kiedy ja bym proponował użyć interface
zamiast type
kiedy możesz. Można przepisać swój record
wpisz (zmieniając go na MyRecord
ze względów umowy o nazwach) jako interface
i wszystko będzie działać:
interface MyRecord<T> { val: T };
type B = MyRecord<B>; // okay
Można nawet przepisać swój func
wpisz (zmieniając go na Func
znowu ze względów umowy o nazwach) jako interface
zmieniając składnia wyrażeń typu funkcji na składnia podpisu połączenia:
interface Func<T> { (arg: T): void }
type C = Func<C>; // okay
Oczywiście, zdarzają się sytuacje, gdy nie można tego zrobić bezpośrednio, na przykład, wbudowanyRecord
typ pożytecznego wykorzystania:
type Darn = Record<string, Darn>; // error
i nie można przepisać skojarzony typ Record
jako interface
. I rzeczywiście, byłoby niebezpieczne starać klucze okrężnymi, jak type NoGood = Record<NoGood, string>
. Jeśli chcesz tylko zrobić Record<string, T>
do wspólnego T
, czy można przepisać to jak interface
:
interface Dictionary<T> extends Record<string, T> { };
type Works = Dictionary<Works>;
Tak, że dość często jest na to sposób korzystać z interface
zamiast type
aby umożliwić ci wyrażać "bezpieczne" typy rekurencyjne.
Link do zabaw dla kodu