カテゴリー
Typescriptで学ぶ!再帰とMapped型でDOM構造を表現するテクニック
※ 当ページには【広告/PR】を含む場合があります。
2025/07/19

さて、今回はTypescriptの型プログラミングの応用的な利用例として、WebページのDOM構造を型で表現するテクニックについて考えてみましょう。
以前の記事では、ファイルシステムのフォルダ構造をTypescriptの型でどう扱うかを紹介しました。
今回はより身近な例として、抽象化したDOM構造をTypescriptの型で表現することに挑戦します。 Typescriptの実践的な型プログラミングの利用例として、その強力さを感じていただければ幸いです。
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
* **ブロック要素**:
前後の要素が改行され、縦に並びます。
`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,
};
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
myDom
html
body
div
span
なお、プロパティ名に数値(
0
1
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
input
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構造を、実際にプログラムで操作できることが分かります。
まとめ
今回は、Typescriptの高度な型プログラミングを活用し、WebページのDOM構造を型で表現するテクニックを考えてみました。
* **回帰的な型定義**:
DOM要素が子要素として自身(ボックス要素)やインライン要素を持つ構造を表現するために、
型自身を再帰的に参照する定義を行いました。
* **Mapped型の活用**:
ボックス要素内の子ノードをプロパティとして表現するために、Mapped型を利用しました。
これにより、子ノードの順序を保証しつつ、柔軟な階層構造を表現できます。
* **実践的な型プログラミング**:
抽象的な概念であるDOM構造を、Typescriptの型システムで具体的に表現できることを示しました。
getElementsByTagNameメソッドのようなタグ名から該当するノードを取得するメソッドも考えても面白そうです。 以上、TypescriptでDOM構造を表現してみるテクニックを考えてみました。
記事を書いた人
ナンデモ系エンジニア
主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。
カテゴリー