history.pushState/replaceState の基本 — history.pushState({}, '', '/page')
history.pushState と history.replaceState は ブラウザの履歴を操作しつつ URL を書き換えるためのメソッドです。ページを再読み込みせずに URL を変更できるので、SPA(シングルページアプリケーション)や検索条件の反映などに使われます。
基本の使い方
// 履歴に新しいエントリを追加(戻る/進むで辿れる)
history.pushState({ page: 1 }, "", "/page1");
// 現在の履歴を置き換え(戻る/進むでは辿れない)
history.replaceState({ page: 2 }, "", "/page2");
JavaScript- 第1引数: 状態オブジェクト(任意のデータ)。
popstateイベントで取り出せる。 - 第2引数: タイトル(現在はほぼ無視される)。空文字でOK。
- 第3引数: 新しいURL(同一オリジン内でのみ変更可能)。
例題1: タブ切り替えでURLを更新(SPA風)
<button id="tabA">タブA</button>
<button id="tabB">タブB</button>
<div id="content"></div>
<script>
const content = document.getElementById("content");
document.getElementById("tabA").addEventListener("click", () => {
content.textContent = "これはタブAの内容";
history.pushState({ tab: "A" }, "", "?tab=A");
});
document.getElementById("tabB").addEventListener("click", () => {
content.textContent = "これはタブBの内容";
history.pushState({ tab: "B" }, "", "?tab=B");
});
// 戻る/進むで状態を復元
window.addEventListener("popstate", (e) => {
if (!e.state) return;
content.textContent = `これはタブ${e.state.tab}の内容`;
});
</script>
HTML- ポイント:
pushStateで履歴を積み、popstateで戻る/進むに対応。 - URL:
?tab=Aや?tab=Bに変わるがページは再読み込みされない。
例題2: 検索条件をURLに反映(replaceState)
<input id="q" placeholder="検索語">
<button id="search">検索</button>
<div id="result"></div>
<script>
const q = document.getElementById("q");
const result = document.getElementById("result");
document.getElementById("search").addEventListener("click", () => {
const keyword = q.value.trim();
result.textContent = `検索結果: ${keyword}`;
// 履歴を置き換え(戻る/進むでは積まない)
history.replaceState({ q: keyword }, "", `?q=${encodeURIComponent(keyword)}`);
});
// 戻る/進むで復元(replaceStateでもpopstateは発火する)
window.addEventListener("popstate", (e) => {
if (e.state?.q) {
result.textContent = `検索結果: ${e.state.q}`;
q.value = e.state.q;
}
});
</script>
HTML- ポイント:
replaceStateは「履歴を増やさず、現在のエントリを更新」。検索条件のように「戻るで不要」な場合に便利。
実務でのコツ
- 同一オリジン制約: URL変更は同じドメイン・プロトコル・ポート内でのみ可能。外部サイトには変更できない。
- popstate イベント: 戻る/進むで発火。
stateオブジェクトを使って画面状態を復元する。 - push vs replace:
- pushState: 履歴を積む → 戻る/進むで辿れる。
- replaceState: 履歴を置き換える → 戻る/進むでは辿れない。
- タイトル引数: 現在はほぼ無視される。空文字で問題なし。
- SPAでの利用: ルーティングライブラリ(React Router, Vue Routerなど)も内部でこれを使っている。
ありがちなハマりポイントと対策
- URLだけ変えても画面が変わらない:
- 対策:
popstateイベントで状態復元処理を書く。
- 対策:
- 外部URLを指定してエラー:
- 対策: 同一オリジン内のパスだけ指定可能。
- 履歴が増えすぎる:
- 対策: 状況に応じて
replaceStateを使う。
- 対策: 状況に応じて
- stateを使わずに復元できない:
- 対策: 必要な情報は第1引数のオブジェクトに入れておく。
練習問題(ページ番号付きリスト)
<ul id="list"></ul>
<button id="next">次へ</button>
<script>
const list = document.getElementById("list");
let page = 1;
function render() {
list.innerHTML = "";
for (let i = 1; i <= 5; i++) {
const li = document.createElement("li");
li.textContent = `Page ${page} - Item ${i}`;
list.appendChild(li);
}
}
document.getElementById("next").addEventListener("click", () => {
page++;
render();
history.pushState({ page }, "", `?page=${page}`);
});
window.addEventListener("popstate", (e) => {
if (e.state?.page) {
page = e.state.page;
render();
}
});
render();
</script>
HTML- 効果: 「次へ」で履歴が積まれ、戻る/進むでページ番号が復元されます。
直感的な指針
- 「履歴を積む」なら pushState、「履歴を置き換える」なら replaceState。
- 必ず popstate で復元処理を書く。
- SPAや検索条件の反映に使うと、URLと画面状態を同期できる。
