web-dev-qa-db-ja.com

TypeScriptで循環参照型を作成する方法は?

私は次のコードを持っています:

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

TypeScriptは次のエラーを表示します。

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

明らかに循環参照は許可されていません。ただし、この種の構造はまだ必要です。これの回避策は何ですか?

29
samvv

TypeScriptの作成者は、再帰型を作成する方法をここで説明します。 https://github.com/Microsoft/TypeScript/issues/3496#issuecomment-12855354

循環参照の回避策は、extends Arrayを使用することです。あなたの場合、これはこの解決策につながります:


type Document = number | string | DocumentArray;

interface DocumentArray extends Array<Document> { }

問題の更新されたサンプルコードを反映するために編集されました。

24

すでに良い答えがありますが、そもそもあなたが望んでいたものに近づくことができると思います。

次のようなものを試すことができます:

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 }]];

NPEの答えと比較すると、文字列と数字を囲むラッパーオブジェクトは必要ありません。

単一の数字または文字列を有効なドキュメントにしたい場合(これはあなたが尋ねたものではなく、NPEの答えが意味するものです)、これを試してください:

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 ];

更新:

配列の代わりにインデックス署名付きのインターフェイスを使用すると、型情報が失われるという欠点があります。 TypeScriptでは、find、map、forEachなどの配列メソッドを呼び出すことはできません。例:

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);
}

これは、DocumentArrayの定義を変更することで解決できます。

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

これを行う1つの方法を次に示します。

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

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

自分自身を参照する型は「再帰型」と呼ばれ、言語仕様の セクション3.11.8 で説明されています。次の抜粋は、試行がコンパイルされない理由を説明しています。

クラスとインターフェースは、内部構造で自身を参照できます...

元の例では、クラスもインターフェースも使用していません。 type aliasを使用します。

13
NPE

NPEが言ったことに基づいて、型は再帰的に自分自身を指すことはできません。あなたが十分だと思う深さのレベルまでこの型を展開できます、例えば:

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

きれいではありませんが、プロパティ値を持つインターフェイスまたはクラスの必要性を取り除きます。

1
ArcSine