SPAをJavaScriptのみで実装してみた
SPA とは
SPA とは、Single Page Application の略で、1 つのページでアプリケーションを実装することを指します。
モダンな JS フレームワークを使っている人は、なじみ深いと思います。
従来の MPA(Multi Page Application)では、ページ遷移するたびにサーバーから HTML を取得し、ブラウザに描画していました。
それに対し、SPA が実装されたページは、Javascript で HTML の一部を差し替えて必要な部分だけを読み込みます。
Vue.js での例
<!DOCTYPE html>
<html lang="">
<body>
<noscript>
<strong>JSが存在しない時の処理</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
Vue.js で SPA を実装する場合、HTML には <div id="app"></div>
という要素が存在し、その中に Vue が解析した html が差し込まれていってアプリケーションが動作します。
この処理は基本的にVue.js
,React
,Angular
,などの JS フレームワーク/ライブラリが行っているので、実装者はその作法に従って実装すれば、容易に SPA のアプリケーションが作られます。
今回はこの一連の処理を、JavaScript のみで実装してみたいと思います。
JavaScript のみで SPA を実装する
フォルダ構成
- index.html
- js
- router.js
- pages
- index.html
- about.html
- profile.html
- 404.html
このルート直下にindex.html
というファイルが初めに読み込まれる単一の HTML ファイルです。
pages
配下の HTML ファイルは JS によって index.html に差し込まれます。
index.html の実装
ルート直下の index.html
<!DOCTYPE html>
<html lang="ja">
<body>
<div>
<nav>
<a href="/" onclick="route()">Home</a>
<a href="/about/" onclick="route()">About</a>
<a href="/profile/" onclick="route()">profile</a>
</nav>
<hr />
<div id="main-page"></div>
</div>
<script src="js/router.js"></script>
</body>
</html>
anchor タグのhref
属性には、ページ遷移先の URL を指定します。
onclick="route()"
は後ほど実装するroute()
関数を呼び出すためのものです。
JS を実装するときに説明します。
<div id="main-page"></div>
ここに、JavaScript によって取得された HTML が差し込まれます。
上のVue.js
の例で言うところの<div id="app"></div>
と同じです。
ちなにみ、この状態で、About
やProfile
ボタンをクリックすると、エラーが発生します。
指定したパスに html は存在しませんので。
route.js の実装
"use strict";
const route = (event) => {
event = event || window.event;
event.preventDefault();
window.history.pushState({}, "", event.target.href);
handleLocation();
};
const routes = {
404: "/pages/404.html",
"/": "/pages/index.html",
"/about/": "/pages/about.html",
"/profile/": "/pages/profile.html",
};
const handleLocation = async () => {
const path = window.location.pathname;
const route = routes[path] || routes[404];
const html = await fetch(route).then((data) => data.text());
document.getElementById("main-page").innerHTML = html;
};
window.addEventListener("popstate", (event) => {
handleLocation();
});
handleLocation();
一つずつ、順を追ってみていきます。
このrouter.js
が読み込まれて、一番初めに実行されるのかhandleLocation
です。
const handleLocation = async () => {
// 現在のパスを取得
const path = window.location.pathname;
// 現在のパスに紐ずくhtmlのパスを取得
const route = routes[path] || routes[404];
// htmlを取得
const html = await fetch(route).then((data) => data.text());
// 取得したhtmlを動的にルート直下のindex.htmlに差し込む
document.getElementById("main-page").innerHTML = html;
};
上記の流れで、JavaScript によって、html が描画されます。
次に anchor タグがクリックされた時の挙動です。
const route = (event) => {
// クリック時のイベントを取得
event = event || window.event;
// ブラウザのデフォルト動作をキャンセル
event.preventDefault();
// ブラウザの履歴に追加
window.history.pushState({}, "", event.target.href);
handleLocation();
};
この関数が行なっていることは、history API を使って、ブラウザの履歴に追加することです。
history API については、こちらを参照してください。
history API については詳しい説明は、省きますが、例えば、about
ボタンをクリックした時に、window.history.pushState({}, "", event.target.href);
を実行後
console.log(window.location.pathname);
を実行すると、/about/
が出力されます。
window.history.pushState
によって、ブラウザの履歴にどんどん追加されていっていると考えると、わかりやすいです。
ボタンを押すたびに、window.history.pushState
が実行されて、ブラウザの履歴に追加されるので、10 回ボタンを押すと、10 個の履歴が追加されます。
最後に、popstate についてです。
window.addEventListener("popstate", () => { handleLocation(); });
popstate
は、ブラウザの戻るボタンや進むボタンが押された時に発火します。
さきほどのwindow.history.pushState
で、ブラウザの履歴に追加しているので、ブラウザの戻るボタンを押すと、一回前の履歴に戻ることができます。
戻った時に、popstate
が発火され、ブラウザの history を元に紐づく html を handleLocation()
で取得してきて、差し込んでいます。
これで戻るや進むボタンが使えるようになりました。
pages 配下の html
中身は好きなように用意してください。
404
<div>
<h1>404 Not Found</h1>
<p>Oh no! It looks like the page you're trying to get to is missing!</p>
</div>
about
<div>
<h1>About</h1>
<p>I am gonne introduce about Me</p>
</div>
index
<div>
<h1>index</h1>
<p>This is an indexPage</p>
</div>
profile
<div>
<h1>Profile</h1>
<p>My name is JavaScript</p>
</div>
まとめ
javascript のみで、SPA を実装してみました。
普段使っているフレームワークを実装してみると、興味深いですね。
参考
Discussion