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

DOM構造について
DOMツリーとノード
<!DOCTYPE html>
<html>
<head>
<title>タイトル</title>
</head>
<body>
<h1>見出し</h1>
<p>段落</p>
</body>
</html>
document
└── html
├── head
│ └── title
│ └── #text タイトル
└── body
├── h1
│ └── #text 見出し
└─── p
└── #text 段落
<html>
<head>
<body>
CSSのBoxとInlineの概念
display
* **ブロック要素**:
前後の要素が改行され、縦に並びます。
`width`や`height`、`margin`、`padding`を自由に指定できます。
例: `<div>`、`<p>`、`<h1>`
* **インライン要素**:
前後の要素が改行されず、横に並びます。
`width`や`height`は指定できず、内容に応じて自動的に決まります。
上下の`margin`は適用されず、`padding`も上下に指定するとレイアウトが崩れる可能性があります。
例: `<span>`、`<a>`、`<strong>`
* **インラインブロック要素**:
インライン要素のように横に並びますが、
ブロック要素のように`width`や`height`、`margin`、`padding`を自由に指定できます。
例: `<input>`、`<button>`
type Inline = {
__type: 'inline',
tag: /** インライン要素名 */,
text: string,
};
type Box = {
__type: 'box',
tag: /** ボックス要素名 */,
text?: string,
};
DOM構造の型を設計する
(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
EmptyBox
__type
tag
text
K
Inline
EmptyBox
Box
DOM
Inline
EmptyBox
Box
myDom
html
body
div
span
0
1
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)"
まとめ
* **回帰的な型定義**:
DOM要素が子要素として自身(ボックス要素)やインライン要素を持つ構造を表現するために、
型自身を再帰的に参照する定義を行いました。
* **Mapped型の活用**:
ボックス要素内の子ノードをプロパティとして表現するために、Mapped型を利用しました。
これにより、子ノードの順序を保証しつつ、柔軟な階層構造を表現できます。
* **実践的な型プログラミング**:
抽象的な概念であるDOM構造を、Typescriptの型システムで具体的に表現できることを示しました。
記事を書いた人
ナンデモ系エンジニア
主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。
カテゴリー