innerHTML と createElement の使い分け(パフォーマンスとベストプラクティス)
要点サマリ
-
大量の静的マークアップを一括で描画するなら
innerHTML(またはinsertAdjacentHTML)が高速になりやすい。 -
細かな差分更新・イベント付与・安全なテキスト挿入なら
createElement(+textContent、DocumentFragment)が堅牢で保守しやすい。 -
信頼できないデータを 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が有利。 -
検索結果の追加読み込み(無限スクロール) →
DocumentFragment+createElementで逐次追加。 -
既存行の数件だけを更新 → 該当ノードを
createElementで差し替え(replaceWith/replaceChildren)。 -
ユーザー入力を表示(コメント、タイトル等) →
textContent(もしくはサニタイズ後に安全な API)。 -
頻繁にクリック/キーハンドラを仕込む UI → イベント委譲+
createElementが安全・明快。
パフォーマンス観点の実務ポイント
-
一括作成・一括差し替え
-
文字列ベース:
container.innerHTML = bigHtml -
DOM ベース:
const frag = new DocumentFragment(); ...; container.replaceChildren(frag)
-
-
オフラインで組み立てる
-
親から切り離したノードや
DocumentFragmentに積み上げ、最後に 1 回で挿入。
-
-
差分更新を最小化
-
変わる部分だけを
replaceWith/before/afterでピンポイント更新。
-
-
再計測を避ける
-
更新中に
offsetHeight等の強制同期レイアウトを起こさない。必要なら最後にまとめて読む。
-
-
テンプレート要素の活用
-
<template>で静的構造を保持し、content.cloneNode(true)を量産して差し込む。
-
セキュリティと安全な挿入
-
ルール: 信頼できない文字列は HTML として挿入しない。
-
テキスト →
node.textContent = value -
どうしても HTML を扱う → 信頼できるサニタイザを通す(フロントでは Trusted Types や実績あるサニタイザの利用を検討)。
-
-
イベントは 属性(
onclick等)でなく addEventListener を基本にする。
アクセシビリティ / イベント / 状態
-
innerHTMLによる置換は フォーカスや選択状態を失うことがある。フォーカス管理が必要な UI は差分更新が望ましい。 -
リスト等は イベント委譲(親に 1 つのリスナ)でパフォーマンスと可読性を両立。
比較表
| 観点 | innerHTML / insertAdjacentHTML | createElement / Fragment |
|---|---|---|
| 初期レンダリング(大量) | 有利(速いことが多い) | 不利になりやすい |
| 細かな差分更新 | 不向き | 有利 |
| イベントの維持 | 失われやすい(再バインド必要) | 維持しやすい |
| セキュリティ | サニタイズ必須 | textContent で安全 |
| コード量 | 少なめ(文字列整形次第) | 多め(だが型安全・明確) |
| 再描画制御 | 粗い(範囲が広い) | 細かい・制御しやすい |
コード例
1) 初期表示:大きなリストを一括描画(innerHTML)
2) 追記:既存 DOM を壊さずに追加(insertAdjacentHTML)
3) 差分更新:DocumentFragment でまとめて追加(createElement)
4) 要素のピンポイント置換(replaceChildren / replaceWith)
5) テンプレート要素で高速増殖
6) イベント委譲(動的要素にも効く)
アンチパターン
-
ユーザー入力を
innerHTMLにそのまま入れる。 -
小さな変更でも、毎回
container.innerHTML = ...で 全体を作り直す。 -
頻繁に DOM を追加しながら、同時にレイアウト情報(
getBoundingClientRect()等)を繰り返し参照して レイアウトスラッシングを起こす。 -
イベントを
onclickのような HTML 属性に直書きする。
実務チェックリスト
-
初期描画は
innerHTML/insertAdjacentHTMLで一括、更新は 差分で。 -
動的テキストは
textContent。HTML を入れる際は サニタイズ。 -
DocumentFragment/replaceChildrenでまとめて入れ替え。 -
イベント委譲で追加要素にも自動対応。
-
パフォーマンス計測は 実データ量で行い、必要なら実装を切り替える。
この方針で、描画速度・保守性・安全性のバランスをとりながら innerHTML と createElement を適切に使い分けられます。
ChatGPT5 生成日:2025/09/11