SlideShare a Scribd company logo
SPA のルーティングの話
NDS Meetup #12
ushiboy
SPA とは
SPA ( Single Page Application )とは
単一の Web ページで構成される Web アプリケーションや Web サイト
デスクトップアプリケーションのような UX を実現する
画面の切り替えは JavaScript による DOM 操作で行う
クライアントサイドで URL に応じた画面を表示(ルーティング)する
SPA ルーティングの方法
2通りの方法がある
Hash
History API
Hash
Hash によるルーティング
URL の Hash (フラグメント識別子)を利用した方法
location.hash
hashchange イベント
http://localhost:8080/path/to#hoge
Hash ( JavaScript でのルートの扱い)
location.hash を使う
遷移先ルートの設定
location.hash = 'hoge';
現在のルートの取得
console.log(location.hash); // -> '#hoge'
(補足) location
http://localhost:8080/path/to?a=1&b=2#hoge
protocol http:
host localhost:8080
hostname localhost
port 8080
origin http://localhost:8080
pathname /path/to
search ?a=1&b=2
hash #hoge
href http://localhost:8080/path/to?a=1&b=2#hoge
Hash ( a タグでの扱い)
a タグでは通常通りに href 属性で定義する
<a href="#hoge">Hoge</a>
<a href="#fuga">Fuga</a>
<a href="#piyo">Piyo</a>
hashchange イベント
Hash が変わった時に発生する
location.hash === '#aaa' の時に location.hash = 'aaa' しても発生しない
イベントリスナーで変更前と変更後の URL が取得できる
window.addEventListener('hashchange', e => {
const { newURL, oldURL } = e;
console.log(newURL); // http://localhost/#test
console.log(oldURL); // http://localhost/
}, false);
ページロード時には発生しない
ブラウザの戻る・進むでは Hash が変われば発生する
Hash でのルーティングの動き
ページロード時
現在のルート( location.hash の値)に応じて画面を表示する
ユーザーの画面遷移操作
location.hash を変更する
hashchange イベント
現在のルート( location.hash の値)に応じて画面を表示する
Hash でのルーティングの特徴
Hash より前の URL の要素(パスとか)は変化しない
http://localhost:8080/path/to#hoge
ブラウザは Hash を HTTP リクエストとしてサーバへ送らない
サーバサイドレンダリングできない
(余談) escaped fragment
Google が過去に出した Hash なルーティングをクロールしてくれる仕組み
現在は Deprecated
「 #! 」みたいな感じでやっていた
https://developers.google.com/webmasters/ajax-crawling/docs/getting-started
History API
History API によるルーティング
History API を利用した方法
location.pathname ( location.search, location.hash )
history.state
history.pushState ( history.replaceState )
popstate イベント
http://localhost:8080/path/to/hoge
History API ( JavaScript でのルートの扱い)
サーバにリクエストが行かないように次のようにする
遷移先ルートの設定
history.pushState(state, title, '/hoge');
現在のルートの取得
console.log(location.pathname); // -> '/hoge'
History API ( a タグでの扱い)
サーバにリクエストが行かないようにクリックのデフォルト挙動を止める
<a href="/hoge">Hoge</a>
function handleClick(e) {
e.preventDefault();
const { href } = e.target;
const state = { /* なんらかの状態 */ };
history.pushState(state, null, href);
}
Array.from(document.querySelectorAll('a')).forEach(el => {
el.addEventListener('click', handleClick, false);
});
popstate イベント
ブラウザの戻る・進むで発生する
history.pushState メソッドを実行したタイミングで起こるわけではない
イベントリスナーで状態が取得できる
window.addEventListener('popstate', e => {
const { state } = e;
console.log(state);
}, false);
ページロード時には発生しない
History API でのルーティングの動き
ページロード時
現在のルート( location.pathname の値)に応じて画面を表示する
history.state から状態を取り出して利用する
ユーザーの画面遷移操作
history.pushState で遷移先ルートを設定する
遷移先ルートの画面を表示する
popstate イベント
現在のルート( location.pathname の値)に応じて画面を表示する
e.state から状態を取り出して利用する
History API でのルーティングの特徴
状態をオブジェクトとして保存できる
同じ origin のパスならどこでも書き換えられる
http://localhost:8080/path/to
サーバ側で rewrite が必要になる
apache -> mod_rewrite
nginx -> rewrite
サーバサイドレンダリングと組み合わせることができる
History API の開発環境
開発用に Web サーバを起動して使っているようなケース
何もしないと URL のパスにアクセスしてしまって 404 になる
historyApiFallback を使う
connect を使っている場合は connect-history-api-fallback をミドルウェアに使う
webpack-dev-server の場合はオプションで有効にする
Hash と History API の比較
Hash History API
難易度 お手軽 いろいろ考慮が必要
サーバサイドの設定 不要 必要
サーバサイドレンダリング できない できる
状態の保持 なし あり
用途 裏画面
Electron アプリ
表画面
URL のルーティング定義
サーバサイドでやってたときと同じ感じ
Hash
一覧画面系だったら #/users
詳細画面系だったら #/users/1
History API
一覧画面系だったら /users
詳細画面系だったら /users/1
ライブラリでの定義フォーマットもサーバサイドと同じ感じ
/users/:id みたいな感じ
まとめ
SPA ではクライアントサイドでルーティングが必要になる
ルーティングの方法は 2 通り
Hash
History API
SPA ルーティングあるある
「 SPA ルーティングあるある」と書きましたが ...
SPA のルーティングでやらかしてきた話
要は失敗談
ブラウザで戻ると壊れる
ブラウザで戻ると壊れる
ルーティング管理外の画面を入れてしまっときに起こる
一覧から選択して詳細に行くやつとか
ツリーを展開していくやつとか
ウィザード的に進んでいくやつとか
一つ前に戻ったつもりが、予想もしていなかった画面に戻る
まったくルーティング入れないとブラウザの新規タブ開いた状態に戻ったりする
⇒ 意識してルーティング管理下に置く
リロードすると壊れる
リロードすると壊れる
状態やパラメータをメモリ中にしか持っていなかった場合に起こる
Ajax でデータ取ってくるためのパラメータがなくて取れないとか
絞り込み条件フォームの入力状態がクリアされてしまうとか
ページネーションのページ番号がリセットされるとか
⇒ 必要に応じてメモリ以外の場所に保持しておく
URL を共有したら壊れる
URL を共有したら壊れる
URL にパラメータが含まれていない場合に起こる
ページネーションのパラメータとか
http://localhost:8080/users?page=10
検索条件のパラメータとか
http://localhost:8080/users?group=developers
⇒ URL (クエリストリングなど)で持つべきものは持っておく
別タブで開けなくなる
別タブで開けなくなる
a タグのクリックイベント制御が雑だと起こる
Ctrl +クリックやミドルクリックとかで別タブで開こうとすると開けない
⇒ 押されているボタンを調べて正しくハンドリングする
el.addEventListener('click', e => {
// button 0:left 1:middle 2:right
if (e.button === 1 || e.shiftKey || e.altKey || e.ctrlKey || e.metaKey) {
return; // skip
}
e.preventDefault();
} , false);
まとめ(反省)
思った以上にブラウザの標準機能は使っている
ブラウザの標準機能を自然に使えるようにする必要がある
開発中も戻る・進むなどの動きをよく確認しておく
状態をどこに・どこまでもたせるかよく考える

More Related Content

SPAのルーティングの話