Typescriptで学ぶ!再帰とMapped型でDOM構造を表現するテクニック


※ 当ページには【広告/PR】を含む場合があります。
2025/07/19
Typescriptでファイルシステムのフォルダ構造を型で表現する
蛸壺の技術ブログ|Typescriptで学ぶ!再帰とMapped型でDOM構造を表現するテクニック



さて、今回はTypescriptの型プログラミングの応用的な利用例として、WebページのDOM構造を型で表現するテクニックについて考えてみましょう。
以前の記事では、ファイルシステムのフォルダ構造をTypescriptの型でどう扱うかを紹介しました。

合同会社タコスキングダム|蛸壺の技術ブログ
Typescriptでファイルシステムのフォルダ構造を型で表現する

TypeScriptでフォルダ構造を表現する



今回はより身近な例として、抽象化したDOM構造をTypescriptの型で表現することに挑戦します。 Typescriptの実践的な型プログラミングの利用例として、その強力さを感じていただければ幸いです。


合同会社タコスキングダム|蛸壺の技術ブログJavascript(js)&Typescript(ts)プログラミング入門〜これから学ぶ人のためのおすすめ書籍&教材の手引き

DOM構造について



WebページはHTMLで記述され、ブラウザによって解析されると「DOM(Document Object Model)」というツリー構造のデータとしてメモリ上に構築されます。 このDOMをJavaScriptから操作することで、Webページのコンテンツや構造、スタイルを動的に変更できます。

DOMツリーとノード



DOMは、Webページの要素を階層的なツリー構造で表現します。 これをDOMツリーと呼びます。
HTMLの各要素は、DOMツリーでは「ノード」と呼ばれ、ドキュメントノード、要素ノード、テキストノード、属性ノード、コメントノードなど、いくつかの種類があります。
例えば、以下のようなシンプルなHTMLは、DOMツリーとして表現されます。

            <!DOCTYPE html>
<html>
    <head>
      <title>タイトル</title>
    </head>
    <body>
      <h1>見出し</h1>
      <p>段落</p>
    </body>
</html>

        

このHTMLは、以下のようなツリー構造で表現されます。

            document
  └── html
       ├── head
       │    └── title
       │          └── #text タイトル
       └── body
            ├── h1
            │    └── #text 見出し
            └─── p
                 └── #text 段落

        
<html><head><body> などのタグはすべてオブジェクトとなり、その下に続く親子関係がツリー構造で表現されます。

CSSのBoxとInlineの概念



DOM要素のレイアウトを考える上で、CSSの
display プロパティによる「ブロック(block)」と「インライン(inline)」の概念は非常に重要です。

            *   **ブロック要素**:
    前後の要素が改行され、縦に並びます。
    `width`や`height`、`margin`、`padding`を自由に指定できます。
    例: `<div>`、`<p>`、`<h1>`
*   **インライン要素**:
    前後の要素が改行されず、横に並びます。
    `width`や`height`は指定できず、内容に応じて自動的に決まります。
    上下の`margin`は適用されず、`padding`も上下に指定するとレイアウトが崩れる可能性があります。
    例: `<span>`、`<a>`、`<strong>`
*   **インラインブロック要素**:
    インライン要素のように横に並びますが、
    ブロック要素のように`width`や`height`、`margin`、`padding`を自由に指定できます。
    例: `<input>`、`<button>`

        

今回は、このBoxとInlineの概念をTypescriptの型に落とし込むことで、抽象化したDOM構造を表現してみましょう。 話を単純化するため、DOM要素には以下のような静的プロパティを部分型にもつことを想定します。

            type Inline = {
    __type: 'inline',
    tag: /** インライン要素名 */,
    text: string,
};

type Box = {
    __type: 'box',
    tag: /** ボックス要素名 */,
    text?: string,
};

        

合同会社タコスキングダム|蛸壺の技術ブログJavascript(js)&Typescript(ts)プログラミング入門〜これから学ぶ人のためのおすすめ書籍&教材の手引き

DOM構造の型を設計する



DOM構造を型として表現するためには、前回と同様に回帰的な型表現と、Mapped型を利用します。
単に
(Box | Inline)[] で子ノードを表現するだけでは、子ノード同士の順序クリティカル性(兄弟関係)がソートなどで容易に壊れてしまう可能性があります。 そこで、オブジェクトのプロパティとして子ノードを保持することで、順序を保証しつつ階層構造を表現します。
実装例は以下のようなものとなります。

            //Inlineを表現した構造
type Inline = {
    __type: 'inline',
    tag: 'span' | 'a' | 'strong',
    text: string,
};

//子ノードを持たないBoxを表現した構造
type EmptyBox = {
    __type: 'box',
    tag: 'html' | 'body' | 'div',
    text?: string,
};

//(空でない)Boxを表現した構造
type Box = EmptyBox & {
    [K in Exclude<string, keyof EmptyBox>]: Inline | EmptyBox | Box
};

//DOM構造
type DOM = Inline | EmptyBox | Box;

const myDom: DOM = {
    __type: "box",
    tag: "html",
    0: {
        __type: 'box',
        tag: 'body',
        0: {
            __type: 'box',
            tag: 'div',
            text: '最初のdiv要素',
            0: {
                __type: 'inline',
                tag: 'span',
                text: '最初のスパン',
            },
            1: {
                __type: 'inline',
                tag: 'span',
                text: '2つ目のスパン',
            }
        }
    }
};

        

このコードでは、まず
Inline 型でインライン要素の基本的な構造を定義しています。
__type プロパティで 'inline' であることを示し、 tag でインライン要素のタグ名( 'span' , 'a' , 'strong' など)を、 text でそのテキストコンテンツを保持します。
次に、
EmptyBox 型で子ノードを持たないボックス要素の構造を定義しています。
__type プロパティで 'box' であることを示し、 tag でボックス要素のタグ名( 'html' , 'body' , 'div' など)を、 text でオプションのテキストコンテンツを保持します。

Box 型は EmptyBox 型を拡張し、子ノードを持つことができるように定義されています。 ここで重要なのが [K in Exclude<string, keyof EmptyBox>]: Inline | EmptyBox | Box の部分です。
これはMapped型を利用しており、
EmptyBox 型が持つプロパティ( __type , tag , text )以外の任意の文字列キー( K )に対して、その値が Inline 型、 EmptyBox 型、または Box 型であることを示しています。 これにより、ボックス要素が再帰的に子要素(インライン要素、空のボックス要素、または子ノードを持つボックス要素)を持つ構造を表現できます。
最後に、
DOM 型は、 Inline 型、 EmptyBox 型、 Box 型のユニオン型として定義されており、任意のDOM要素を表すことができます。

myDom という定数では、実際にこの型を使ってHTMLの基本的なDOM構造を表現したインスタンスの例を示しています。

html タグの中に body タグがあり、その中に div タグ、さらにその中に2つの span タグが入れ子になっている階層構造が、Typescriptの型によって表現されていることが分かります。
なお、プロパティ名に数値(
0 , 1 , ...)を使用するのは、DOMノードの順序を表現しています。


合同会社タコスキングダム|蛸壺の技術ブログJavascript(js)&Typescript(ts)プログラミング入門〜これから学ぶ人のためのおすすめ書籍&教材の手引き

DOM構造の型を使ったメソッドの実装例



設計したDOM構造の型を使って、簡単な操作を行うメソッドを実装してみましょう。 ここでは、DOM構造の型インスタンスからXPath風のDOM要素一覧を返すメソッド
listDom を考えます。

            function listDom(input:DOM, parent: string[] | null = null) {
    if (input == null) return;

    if (parent == null) parent = [input.tag];

    console.log('PATH:', parent.join(" > "));

    for (const key in input) {
        if (['__type', 'tag', 'text'].includes(key)) continue;
        const _child = (input as Box)[key];
        if (_child) {
            listDom(_child, [...parent, `${_child.tag}(${key})`]);
        }
    }
}

listDom(myDom);

        

この
listDom 関数は、再帰的にDOM構造を探索し、各要素のパスをコンソールに出力します。

input が現在のDOM要素、 parent が親要素までのパスを保持します。 ループ内で __type , tag , text 以外のプロパティ(つまり子ノード)を抽出し、再帰的に listDom を呼び出すことで、ツリー構造を辿っていきます。
上記の
myDom インスタンスを listDom 関数で処理すると、以下のような出力が得られます。

            "PATH:",  "html"
"PATH:",  "html > body(0)"
"PATH:",  "html > body(0) > div(0)"
"PATH:",  "html > body(0) > div(0) > span(0)"
"PATH:",  "html > body(0) > div(0) > span(1)"

        

このように、Typescriptの型定義によって表現されたDOM構造を、実際にプログラムで操作できることが分かります。


合同会社タコスキングダム|蛸壺の技術ブログJavascript(js)&Typescript(ts)プログラミング入門〜これから学ぶ人のためのおすすめ書籍&教材の手引き

まとめ



今回は、Typescriptの高度な型プログラミングを活用し、WebページのDOM構造を型で表現するテクニックを考えてみました。

            *   **回帰的な型定義**:
    DOM要素が子要素として自身(ボックス要素)やインライン要素を持つ構造を表現するために、
    型自身を再帰的に参照する定義を行いました。
*   **Mapped型の活用**:
    ボックス要素内の子ノードをプロパティとして表現するために、Mapped型を利用しました。
    これにより、子ノードの順序を保証しつつ、柔軟な階層構造を表現できます。
*   **実践的な型プログラミング**:
    抽象的な概念であるDOM構造を、Typescriptの型システムで具体的に表現できることを示しました。

        

getElementsByTagNameメソッドのようなタグ名から該当するノードを取得するメソッドも考えても面白そうです。 以上、TypescriptでDOM構造を表現してみるテクニックを考えてみました。
記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。

合同会社タコスキングダム|蛸壺の技術ブログJavascript(js)&Typescript(ts)プログラミング入門〜これから学ぶ人のためのおすすめ書籍&教材の手引き