DOM操作の「作法」:JavaScriptでインライン要素を美しく生成する最適解
フロントエンドの世界に足を踏み入れると、誰もが一度は `innerHTML` の誘惑に駆られます。「`element.innerHTML = ‘text‘` と書けば一瞬で終わるのに、なぜわざわざ `document.createElement` を使う必要があるのか?」と。
実務で複雑なアプリケーションを構築していると、この問いの答えが「単なる慣習」ではなく「保守性とセキュリティの境界線」であることが痛いほどわかってくるはずです。今日は、中級エンジニアの皆さんが、明日からのコードの質を一段引き上げられるよう、インライン要素を動的に生成する際の「現場の作法」を深掘りします。
—
なぜ今さら `createElement` なのか
ブラウザのレンダリングエンジンは、`innerHTML` に文字列を渡されると、パーサーを起動してHTML文字列を解析し、DOMツリーを再構築します。これに対し、`document.createElement` は、直接ブラウザのAPIを叩いてメモリ上にノードを作成します。
特にインライン要素(`span`, `a`, `strong` など)を頻繁に操作する場合、`innerHTML` は親要素の中身を一度すべて破棄して作り直すため、既存のイベントリスナーやフォーカス状態が吹き飛ぶリスクを常に孕んでいます。「必要な箇所だけを、スマートに挿入する」。 これが、モダンなフロントエンド開発における鉄則です。
—
実践:堅牢なインライン要素生成のテンプレート
単に要素を作るだけでなく、属性の設定やテキストの安全な挿入までを網羅した、実務でそのまま使える汎用関数を紹介します。
/
- インライン要素を安全に生成し、指定した親要素に追加するヘルパー
- @param {string} tagName – 生成するタグ名 (‘span’, ‘a’ など)
- @param {Object} props – 属性やテキストコンテンツのオプション
- @param {HTMLElement} parent – 追加先の親要素
/
const createAndAppend = (tagName, props = {}, parent) => {
// 1. ノードの生成
const el = document.createElement(tagName);
// 2. 属性(href, title, idなど)の適用
Object.entries(props).forEach(([key, value]) => {
if (key === ‘text’) {
// セキュリティを考慮し、textContentで安全に注入
el.textContent = value;
} else if (key === ‘className’) {
el.className = value;
} else {
// その他の属性(hrefなど)を設定
el.setAttribute(key, value);
}
});
// 3. DOMツリーへの挿入
parent.appendChild(el);
return el; // 後の操作のために生成した要素を返すのがプロの流儀
};
// — 利用シーン —
const container = document.querySelector(‘#content-area’);
// リンク付きの装飾テキストを動的に生成
createAndAppend(‘a’, {
href: ‘https://example.com’,
text: ‘詳細はこちら’,
className: ‘link-item’,
target: ‘_blank’
}, container);
// 時間情報をstrongで強調して追記
const timeEl = createAndAppend(‘time’, { text: ‘2023-10-27’ }, container);
timeEl.classList.add(‘date-highlight’); // 後からクラスを追加する余地を残す
—
ブラウザの裏側で起きていること
`appendChild` を実行した瞬間、ブラウザの内部では「Reflow(再レイアウト)」と「Repaint(再描画)」の計算が走ります。
もし、リストアイテムを100個生成する際に、ループ内で毎回 `appendChild` を呼ぶと、ブラウザはそのたびにレイアウト計算を行い、パフォーマンスが著しく低下します。これを回避するための現場の知恵が「DocumentFragment」です。
パフォーマンス向上のためのワンポイントアドバイス
DOMツリーに直接触れる回数を最小限に抑えるには、まずメモリ上の仮想的な箱(Fragment)にすべてのインライン要素を詰め込み、最後にそれをメインのDOMへ一気に挿入します。
const fragment = document.createDocumentFragment();
// 100個のspanを作る場合
for (let i = 0; i < 100; i++) {
const span = document.createElement('span');
span.textContent = `項目 ${i}`;
fragment.appendChild(span);
}
// ここで初めてDOMを更新(1回だけのリフローで済む)
document.body.appendChild(fragment);
---
最後に:なぜ「インライン要素」にこだわるのか
`div` や `section` といったブロック要素と異なり、`span` や `a`、`code` といったインライン要素は、文章のコンテキストを司る重要な存在です。これらを動的に生成するということは、「ドキュメントの文脈をプログラムが操作する」という、ウェブの本来あるべき姿に直結しています。
ライブラリに頼れば一瞬のコードも、その裏側にある `createElement` の挙動を知っているか否かで、トラブルシューティングの際の引き出しの数が大きく変わります。
皆さんが書くコードが、次に読む誰かにとっての「教科書」になるような、丁寧で思慮深い実装をこれからも心がけてください。困ったときは、いつでもこの基本に戻りましょう。DOM操作の基礎こそが、フロントエンドエンジニアの真の実力です。

コメント