web-dev-qa-db-fra.com

Comment créer un type référencé de façon circulaire dans TypeScript?

J'ai le code suivant:

type Document = number | string | Array<Document>;

TypeScript se plaint de l'erreur suivante:

test.ts(7,6): error TS2456: Type alias 'Document' circularly references itself.

Les références clairement circulaires ne sont pas autorisées. Cependant, j'ai toujours besoin de ce type de structure. Quelle serait une solution pour cela?

29
samvv

Le créateur de TypeScript explique comment créer des types récursifs ici: https://github.com/Microsoft/TypeScript/issues/3496#issuecomment-12855354

La solution de contournement pour la référence circulaire consiste à utiliser extends Array. Dans votre cas, cela conduirait à cette solution:


type Document = number | string | DocumentArray;

interface DocumentArray extends Array<Document> { }

Modifié pour refléter l'exemple de code mis à jour en question.

24
Kristian Hanekamp

Nous avons déjà de bonnes réponses, mais je pense que nous pouvons nous rapprocher de ce que vous vouliez en premier lieu:

Vous pouvez essayer quelque chose comme ceci:

interface Document {
    [index: number]: number | string | Document;
}

// compiles
const doc1: Document = [1, "one", [2, "two", [3, "three"]]];

// fails with "Index signatures are incompatible" which probably is what you want
const doc2: Document = [1, "one", [2, "two", { "three": 3 }]];

Par rapport à la réponse de NPE, vous n'avez pas besoin d'objets wrapper autour des chaînes et des nombres.

Si vous voulez qu'un seul numéro ou chaîne soit un document valide (ce qui n'est pas ce que vous avez demandé, mais ce que la réponse de NPE implique), vous pouvez essayer ceci:

type ScalarDocument = number | string;
interface DocumentArray {
    [index: number]: ScalarDocument | DocumentArray;
}
type Document = ScalarDocument | DocumentArray;

const doc1: Document = 1;
const doc2: Document = "one";
const doc3: Document = [ doc1, doc2 ];

Mise à jour:

L'utilisation d'une interface avec une signature d'index au lieu d'un tableau présente l'inconvénient de perdre des informations de type. TypeScript ne vous permet pas d'appeler des méthodes de tableau comme find, map ou forEach. Exemple:

type ScalarDocument = number | string;
interface DocumentArray {
    [index: number]: ScalarDocument | DocumentArray;
}
type Document = ScalarDocument | DocumentArray;

const doc1: Document = 1;
const doc2: Document = "one";
const doc3: Document = [ doc1, doc2 ];
const doc = Math.random() < 0.5 ? doc1 : (Math.random() < 0.5 ? doc2 : doc3);

if (typeof doc === "number") {
    doc - 1;
} else if (typeof doc === "string") {
    doc.toUpperCase();
} else {
    // fails with "Property 'map' does not exist on type 'DocumentArray'"
    doc.map(d => d);
}

Cela peut être résolu en modifiant la définition de DocumentArray:

interface DocumentArray extends Array<ScalarDocument | DocumentArray> {}
16
Daniel Faber

Voici une façon de procéder:

class Doc {
  val: number | string | Doc[];
}

let doc1: Doc = { val: 42 };
let doc2: Doc = { val: "the answer" };
let doc3: Doc = { val: [doc1, doc2] };

Les types qui se référencent eux-mêmes sont appelés "types récursifs" et sont décrits dans section 3.11.8 de la spécification de langue. L'extrait suivant explique pourquoi votre tentative ne se compile pas:

Les classes et interfaces peuvent se référencer dans leur structure interne ...

Votre exemple d'origine n'utilise ni classe ni interface; il utilise un alias de type.

13
NPE

En se basant sur ce que NPE a dit, les types ne peuvent pas se désigner récursivement, vous pouvez dérouler ce type à n'importe quel niveau de profondeur que vous considérez suffisant, par exemple:

type Document = [number|string|[number|string|[number|string|[number|string]]]]

Pas joli, mais supprime le besoin d'une interface ou d'une classe avec une valeur de propriété.

1
ArcSine