Por que a máquina não permitir referências circulares em genéricos?

0

Pergunta

aqui os exemplos onde o tipo diretamente referências a si mesmo em sua definição, mas quando abstraído através de um genérico é completamente falha.

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!?
types typescript
2021-11-23 20:48:45
2

Melhor resposta

3

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

2021-11-23 21:31:48

obrigado! isso é legal e útil!
radiish
1

Vamos dissecar estas cenário, um por um.

Cenário 1

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

É interessante isso é permitido. Eu não vejo como você poderia ser capaz de construir uma instância que satisfaça a este tipo:

const A: a = {
  val: {
    val: {
      // It will always error out at the most inner node.
    }
  }
}

Cenário 2

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

Esta não é uma referência circular e pode ser satisfeito, como este:

const B: record<string> = {
  val: "test"
}

Cenário 3

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

Faz sentido para mim que isto não funciona. Assim como no Cenário 1, não haveria maneira de construir uma instância que satisfaz essa restrição.

Cenário 4

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

Esta não é uma referência circular e pode ser satisfeito, como este:

const C: func<string> = (arg: string) => {}

Cenário 5

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

Faz sentido para mim que isto não funciona. Assim como no Cenário 1, não haveria maneira de construir uma instância que satisfaz essa restrição.

Cenário 6

type d = (arg: d) => void; // <-- works!?

Eu, na verdade, pode escrever uma função para atender a esta restrição, mas não tenho a certeza que ele está a deixar-me:

const D: d = (arg) => {}
D(D)
2021-11-23 21:34:19

Em outros idiomas

Esta página está em outros idiomas

Русский
..................................................................................................................
Italiano
..................................................................................................................
Polski
..................................................................................................................
Română
..................................................................................................................
한국어
..................................................................................................................
हिन्दी
..................................................................................................................
Français
..................................................................................................................
Türk
..................................................................................................................
Česk
..................................................................................................................
ไทย
..................................................................................................................
中文
..................................................................................................................
Español
..................................................................................................................
Slovenský
..................................................................................................................