Consulte o microsoft/TypeScript#41164 para uma canônico resposta a esta pergunta.
O TypeScript não permitir referências circulares em genérico interfaces e genérico classes, desde interfaces e instâncias de classe estaticamente conhecido propriedade/membro/método tecla des e para qualquer circularidade acontece em "safe" lugares como a propriedade de valor des ou parâmetros de método ou tipo de retorno.
interface Interface<T> { val: T }
type X = Interface<X> // okay
class Class<T> { method(arg: T): void { } }
type Y = Class<Y> // okay
Mas para o genérico tipo de aliases não há nenhuma garantia. Tipo de aliases podem ter qualquer estrutura que qualquer tipo anônimo pode ter, portanto, o potencial de circularidade não é restrita ao recursiva de árvore de objetos:
type Safe<T> = { val: T };
type Unsafe<T> = T | { val: string };
Quando o compilador cria uma instância de um tipo genérico, ele adia a sua avaliação; não imediatamente tenta totalmente calcular o tipo resultante. Tudo o que vê é o formulário:
type WouldBeSafe = Safe<WouldBeSafe>;
type WouldBeUnsafe = Unsafe<WouldBeUnsafe>;
Os dois têm a mesma aparência para que o compilador... type X = SomeGenericTypeAlias<X>
. Ele não pode "ver" que WouldBeSafe
seria bom:
//type WouldBeSafe = { val: WouldBeSafe }; // would be okay
enquanto WouldBeUnsafe
seria um problema:
//type WouldBeUnsafe = WouldBeUnsafe | { val: string }; // would be error
Pois não consegue ver a diferença, e porque pelo menos alguns usos seria circular ilegalmente, ele só proíbe todos eles.
Então, o que você pode fazer? Este é um daqueles casos em que eu gostaria de sugerir o uso de interface
em vez de type
quando você pode. Você pode reescrever a sua record
tipo (alterando-o para MyRecord
para a convenção de nomenclatura razões) como um interface
e tudo vai funcionar:
interface MyRecord<T> { val: T };
type B = MyRecord<B>; // okay
Você pode até reescrever a sua func
tipo (alterando-o para Func
para a convenção de nomenclatura razões de novo), como uma interface
alterando o tipo de função de expressão de sintaxe em uma chamada de assinatura sintaxe:
interface Func<T> { (arg: T): void }
type C = Func<C>; // okay
É claro que existem situações onde você não pode fazer isso diretamente, tais como o built-in Record
utilitário tipo:
type Darn = Record<string, Darn>; // error
e você não pode reescrever o mapeado o tipo de Record
como um interface
. E, de fato, seria inseguro para tentar fazer com que as teclas circular, como type NoGood = Record<NoGood, string>
. Se você quiser apenas fazer Record<string, T>
para generic T
, você pode reescrever que como um interface
:
interface Dictionary<T> extends Record<string, T> { };
type Works = Dictionary<Works>;
Portanto, há muitas vezes uma maneira de usar um interface
em vez de type
para permitir que você express "seguro" recursiva tipos.
Parque infantil link para o código de