Pourquoi ne tapuscrit autorise pas les références circulaires dans les génériques?

0

La question

ici, les exemples de cas où le type fait directement référence à lui-même dans sa définition, mais quand prélevée par l'intermédiaire d'un générique il échoue complètement.

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

La meilleure réponse

3

Voir microsoft/Tapuscrit#41164 pour un canoniques réponse à cette question.

Tapuscrit ne permettent références circulaires dans les génériques des interfaces et des génériques de classes, depuis les interfaces et les instances de classe ont connu statiquement propriété/membre/méthode clé des et ainsi, toute la circularité qui se passe dans "sécurité" des endroits comme la propriété de la valeurs ou des paramètres de la méthode ou le type de retour.

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

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

Mais pour le générique de type alias il n'y a aucune garantie. Type d'alias peut avoir n'importe quelle structure de tout type anonyme peut avoir, de sorte que le potentiel de la circularité n'est pas limité à récursive de l'arbre des objets similaires à:

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

Lorsque le compilateur instancie un type générique, il reporte son évaluation; elle n'a pas immédiatement essayer de bien calculer le type résultant. Tous voit il est de la forme:

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

Les deux se ressemblent pour le compilateur... type X = SomeGenericTypeAlias<X>. Il ne peut pas "voir" que WouldBeSafe serait bien:

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

alors que WouldBeUnsafe serait un problème:

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

Depuis il ne peut pas voir la différence, et parce qu'au moins certains usages seraient illégalement circulaire, il est juste interdit à tous d'entre eux.


Alors, que pouvez-vous faire? C'est l'un de ces cas où, je te suggère de l'aide interface au lieu de type quand vous le pouvez. Vous pouvez réécrire votre record type (changement de MyRecord pour la convention de nommage des raisons) comme un interface et tout fonctionnera:

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

Vous pouvez même réécrire votre func type (changement de Func pour la convention de nommage des raisons encore) comme un interface en changeant le type de fonction de l'expression de la syntaxe d'un appel de la signature de la syntaxe:

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

Bien sûr, il ya des situations où vous ne pouvez pas le faire directement, telles que le haut- Record utilitaire de type:

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

et vous ne pouvez pas réécrire l' mappé type Record comme un interface. Et en effet, il serait dangereux d'essayer de rendre les clés de la circulaire, comme type NoGood = Record<NoGood, string>. Si vous voulez seulement faire Record<string, T> pour le générique Tvous pouvez réécrire que comme un interface:

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

Donc, il y a assez souvent une façon d'utiliser un interface au lieu de type pour vous permettre de vous exprimer "safe" récursive types.

Aire de jeux lien vers le code

2021-11-23 21:31:48

merci! c'est cool et utile!
radiish
1

Nous allons disséquer ces scénarios, un par un.

Scénario 1

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

C'est intéressant, c'est permis. Je ne vois pas comment vous pourriez être en mesure de construire une instance qui permettrait de satisfaire ce type:

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

Scénario 2

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

Ce n'est pas une référence circulaire et peut être satisfait comme ceci:

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

Scénario 3

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

Il me semble logique que cela ne fonctionne pas. Tout comme dans le Scénario 1, il n'y aurait pas moyen de construire un exemple qui répond à cette contrainte.

Scénario 4

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

Ce n'est pas une référence circulaire et peut être satisfait comme ceci:

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

Scénario 5

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

Il me semble logique que cela ne fonctionne pas. Tout comme dans le Scénario 1, il n'y aurait pas moyen de construire un exemple qui répond à cette contrainte.

Scénario 6

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

Je peux effectivement écrire une fonction pour satisfaire cette contrainte, mais je ne suis pas sûr de ce qu'il m':

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

Dans d'autres langues

Cette page est dans d'autres langues

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