Dlaczego typescript nie dopuszcza odwołań cyklicznych w ogólnych wyrażeniach?

0

Pytanie

oto przykłady, kiedy typ bezpośrednio odwołuje się w swojej definicji, ale абстрагировании za pomocą uniwersalnego on całkowicie zawodzi.

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

Najlepsza odpowiedź

3

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

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

2021-11-23 21:31:48

Dziękuję! to jest fajne i przydatne!
radiish
1

Spójrzmy na te scenariusze, jeden po drugim.

Scenariusz 1

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

Ciekawe, że jest to dozwolone. Nie rozumiem, jak można było utworzyć instancję, który zadowolił by tego typu:

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

Scenariusz 2

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

To nie kołowa link i może być uwzględniony w następujący sposób:

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

Scenariusz 3

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

Dla mnie ma sens, że to nie działa. Podobnie jak w scenariuszu 1, nie byłoby sposobu, aby utworzyć instancję, która spełnia tego wymogu.

Scenariusz 4

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

To nie kołowa link i może być uwzględniony w następujący sposób:

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

Scenariusz 5

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

Dla mnie ma sens, że to nie działa. Podobnie jak w scenariuszu 1, nie byłoby sposobu, aby utworzyć instancję, która spełnia tego wymogu.

Scenariusz 6

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

Ja naprawdę mogę napisać funkcję, spełniające tego wymogu, ale nie jestem pewien, że to daje mi:

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

W innych językach

Ta strona jest w innych językach

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