Почему typescript не допускает циклических ссылок в общих выражениях?

0

Вопрос

вот примеры, когда тип напрямую ссылается на себя в своем определении, но при абстрагировании с помощью универсального он полностью терпит неудачу.

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

Лучший ответ

3

См. microsoft/TypeScript#41164 для получения канонического ответа на этот вопрос.

Машинопись допускает циклические ссылки в универсальных интерфейсах и универсальных классах, поскольку интерфейсы и экземпляры классов имеют статически известные ключи свойств/элементов/методов, и поэтому любая цикличность происходит в "безопасных" местах, таких как значения свойств или параметры метода или тип возвращаемого значения.

interface Interface<T> { val: T }
type X = Interface<X> // okay

class Class<T> { method(arg: T): void { } }
type Y = Class<Y> // okay

Но для общих псевдонимов типов такой гарантии нет. Псевдонимы типов могут иметь любую структуру, которую может иметь любой анонимный тип, поэтому потенциальная цикличность не ограничивается рекурсивными древовидными объектами:

type Safe<T> = { val: T };
type Unsafe<T> = T | { val: string };

Когда компилятор создает экземпляр универсального типа, он откладывает его оценку; он не сразу пытается полностью вычислить результирующий тип. Все, что он видит, - это форма:

type WouldBeSafe = Safe<WouldBeSafe>; 
type WouldBeUnsafe = Unsafe<WouldBeUnsafe>; 

Оба они выглядят одинаково для компилятора... type X = SomeGenericTypeAlias<X>. Он не может "видеть", что WouldBeSafe все было бы в порядке:

//type WouldBeSafe = { val: WouldBeSafe }; // would be okay

пока WouldBeUnsafe это было бы проблемой:

//type WouldBeUnsafe = WouldBeUnsafe | { val: string }; // would be error

Поскольку он не видит разницы и поскольку, по крайней мере, некоторые виды использования были бы незаконно запрещены, он просто запрещает их все.


Итак, что вы можете сделать? Это один из тех случаев, когда я бы предложил использовать interface вместо type когда сможешь. Вы можете переписать свой record введите (изменив его на MyRecord по соображениям соглашения об именах) в качестве interface и все будет работать:

interface MyRecord<T> { val: T };
type B = MyRecord<B>; // okay

Вы даже можете переписать свой func введите (изменив его на Func опять же по соображениям соглашения об именах) в качестве interface изменив синтаксис выражения типа функции на синтаксис подписи вызова:

interface Func<T> { (arg: T): void }
type C = Func<C>; // okay

Конечно, бывают ситуации, когда вы не можете сделать это напрямую, например, встроенныйRecord тип полезного использования:

type Darn = Record<string, Darn>; // error

и вы не можете переписать сопоставленный тип Record в качестве interface. И действительно, было бы небезопасно пытаться сделать ключи круговыми, как type NoGood = Record<NoGood, string>. Если вы только хотите сделать Record<string, T> для общего T, вы можете переписать это как interface:

interface Dictionary<T> extends Record<string, T> { };
type Works = Dictionary<Works>;

Так что довольно часто есть способ использовать interface вместо type чтобы позволить вам выражать "безопасные" рекурсивные типы.

Ссылка на игровую площадку для кода

2021-11-23 21:31:48

Спасибо! это круто и полезно!
radiish
1

Давайте разберем эти сценарии один за другим.

Сценарий 1

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

Интересно, что это разрешено. Я не понимаю, как вы могли бы создать экземпляр, который удовлетворял бы этому типу:

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

Сценарий 2

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

Это не круговая ссылка и может быть удовлетворено следующим образом:

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

Сценарий 3

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

Для меня имеет смысл, что это не работает. Как и в сценарии 1, не было бы способа создать экземпляр, удовлетворяющий этому ограничению.

Сценарий 4

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

Это не круговая ссылка и может быть удовлетворено следующим образом:

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

Сценарий 5

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

Для меня имеет смысл, что это не работает. Как и в сценарии 1, не было бы способа создать экземпляр, удовлетворяющий этому ограничению.

Сценарий 6

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

Я действительно могу написать функцию, удовлетворяющую этому ограничению, но я не уверен, что это дает мне:

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

На других языках

Эта страница на других языках

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

Популярное в этой категории

Популярные вопросы в этой категории