innerHTML と createElement の使い分け(パフォーマンスとベストプラクティス)

要点サマリ

  • 大量の静的マークアップを一括で描画するなら innerHTML(または insertAdjacentHTML)が高速になりやすい。

  • 細かな差分更新・イベント付与・安全なテキスト挿入なら createElement(+textContentDocumentFragment)が堅牢で保守しやすい。

  • 信頼できないデータを HTML として挿入しない。テキストは textContent、HTML は必ずサニタイズ。

  • 再描画の最適化には「オフ DOM で組み立て→一括挿入」や replaceChildren()DocumentFragment を活用。


それぞれの特徴

innerHTML / insertAdjacentHTML

  • 長所

    • 文字列から DOM を一気に構築するため、大きな塊の初期描画は高速になりやすい。

    • 既存ツール/テンプレートから HTML をそのまま出力しやすい。

    • insertAdjacentHTML('beforeend', html) を使えば、既存ノードの置換を避けつつ追記できる。

  • 短所 / 注意点

    • パースと再構築が毎回発生。部分差し替えでもコンテナ配下を広く作り直しやすい。

    • 置き換え範囲内の イベントリスナ・状態が失われる(再バインドが必要)。

    • XSS リスク。外部入力をそのまま入れない(必ずサニタイズ)。

    • <script> は基本的に実行されない一方、属性ベースのイベントonclick 等)混入は設計上避ける。

createElement / append / replaceChildren

  • 長所

    • ノード単位の差分更新に向く。必要な場所だけ追加・入れ替えできる。

    • textContent により 安全なテキスト挿入が容易(HTML として解釈されない)。

    • イベントの付与・委譲と相性が良く、保守性が高い

    • DocumentFragmentオフ DOM 合成 → 一括挿入により reflow を抑制しやすい。

  • 短所

    • 大量の要素を 1 個ずつ組み立てると コード量が増えがち。場合によっては初期レンダリングが innerHTML より遅い。


代表的な使い分け指針

  • 初期表示で 1000 行のリストを一発で描くinnerHTML / insertAdjacentHTML が有利。

  • 検索結果の追加読み込み(無限スクロール)DocumentFragmentcreateElement で逐次追加。

  • 既存行の数件だけを更新 → 該当ノードを createElement で差し替え(replaceWith / replaceChildren)。

  • ユーザー入力を表示(コメント、タイトル等) → textContent(もしくはサニタイズ後に安全な API)。

  • 頻繁にクリック/キーハンドラを仕込む UI → イベント委譲+createElement が安全・明快。


パフォーマンス観点の実務ポイント

  1. 一括作成・一括差し替え

    • 文字列ベース: container.innerHTML = bigHtml

    • DOM ベース: const frag = new DocumentFragment(); ...; container.replaceChildren(frag)

  2. オフラインで組み立てる

    • 親から切り離したノードや DocumentFragment に積み上げ、最後に 1 回で挿入。

  3. 差分更新を最小化

    • 変わる部分だけを replaceWith / before / after でピンポイント更新。

  4. 再計測を避ける

    • 更新中に offsetHeight 等の強制同期レイアウトを起こさない。必要なら最後にまとめて読む。

  5. テンプレート要素の活用

    • <template> で静的構造を保持し、content.cloneNode(true) を量産して差し込む。


セキュリティと安全な挿入

  • ルール: 信頼できない文字列は HTML として挿入しない

    • テキスト → node.textContent = value

    • どうしても HTML を扱う → 信頼できるサニタイザを通す(フロントでは Trusted Types や実績あるサニタイザの利用を検討)。

  • イベントは 属性(onclick 等)でなく addEventListener を基本にする。


アクセシビリティ / イベント / 状態

  • innerHTML による置換は フォーカスや選択状態を失うことがある。フォーカス管理が必要な UI は差分更新が望ましい。

  • リスト等は イベント委譲(親に 1 つのリスナ)でパフォーマンスと可読性を両立。


比較表

観点 innerHTML / insertAdjacentHTML createElement / Fragment
初期レンダリング(大量) 有利(速いことが多い) 不利になりやすい
細かな差分更新 不向き 有利
イベントの維持 失われやすい(再バインド必要) 維持しやすい
セキュリティ サニタイズ必須 textContent で安全
コード量 少なめ(文字列整形次第) 多め(だが型安全・明確)
再描画制御 粗い(範囲が広い) 細かい・制御しやすい

コード例

1) 初期表示:大きなリストを一括描画(innerHTML)

html
<ul id="users"></ul> <script> const data = Array.from({length: 2000}, (_, i) => ({ id: i+1, name: `User ${i+1}` })); const html = data.map(u => `<li data-id="${u.id}">${u.name}</li>`).join(""); document.getElementById("users").innerHTML = html; // 一括描画で速いことが多い </script>

2) 追記:既存 DOM を壊さずに追加(insertAdjacentHTML)

js
const list = document.getElementById("users"); list.insertAdjacentHTML("beforeend", `<li data-id="2001">User 2001</li>`);

3) 差分更新:DocumentFragment でまとめて追加(createElement)

js
const list = document.getElementById("users"); const frag = new DocumentFragment(); for (let i = 2002; i <= 2100; i++) { const li = document.createElement("li"); li.dataset.id = String(i); li.textContent = `User ${i}`; // テキストは textContent で安全 frag.append(li); } list.append(frag); // 一括追加で reflow を最小化

4) 要素のピンポイント置換(replaceChildren / replaceWith)

js
const detail = document.getElementById("detail"); const card = document.createElement("article"); card.className = "card"; card.innerHTML = `<h2>Title</h2><p>Description</p>`; // 内部は固定で安全に組み立てる detail.replaceChildren(card); // まとめて置換

5) テンプレート要素で高速増殖

html
<template id="row-tpl"> <tr><td class="id"></td><td class="name"></td></tr> </template> <table><tbody id="tbody"></tbody></table> <script> const tpl = document.getElementById("row-tpl"); const tbody = document.getElementById("tbody"); const frag = new DocumentFragment(); for (let i = 1; i <= 1000; i++) { const clone = tpl.content.cloneNode(true); clone.querySelector(".id").textContent = i; clone.querySelector(".name").textContent = `Item ${i}`; frag.append(clone); } tbody.append(frag); </script>

6) イベント委譲(動的要素にも効く)

js
const list = document.getElementById("users"); list.addEventListener("click", (e) => { const li = e.target.closest("li"); if (!li) return; console.log("clicked", li.dataset.id); });

アンチパターン

  • ユーザー入力を innerHTML にそのまま入れる。

  • 小さな変更でも、毎回 container.innerHTML = ...全体を作り直す

  • 頻繁に DOM を追加しながら、同時にレイアウト情報(getBoundingClientRect() 等)を繰り返し参照して レイアウトスラッシングを起こす。

  • イベントを onclick のような HTML 属性に直書きする。


実務チェックリスト

  • 初期描画は innerHTML / insertAdjacentHTML で一括、更新は 差分で。

  • 動的テキストは textContent。HTML を入れる際は サニタイズ

  • DocumentFragment / replaceChildren でまとめて入れ替え。

  • イベント委譲で追加要素にも自動対応。

  • パフォーマンス計測は 実データ量で行い、必要なら実装を切り替える。

この方針で、描画速度・保守性・安全性のバランスをとりながら innerHTMLcreateElement を適切に使い分けられます。

ChatGPT5 生成日:2025/09/11