背景 SPAのルーティングって何?
jsnoteでサンプルコードを共有するときに, 普通のWEBページみたいにURLで渡せると便利だなと思いました. GitHubとかコード一つ一つにURLついているし, あんな感じにできたらいいなと思いました. でもどうやったらできるかわかりませんでした. サーバがテンプレートを返しているとしたら, GitHub Pages(静的ファイルだけ配信できる)で真似することはできないけど, なんとかならないかなぁと思っていました.
ネットで調べていたら, 自分がやりたいことはSPAのルーティングだと分かりました. そして2つの記事を見つけて参考にさせていただきました.
https://www.slideshare.net/ushiboy/spa-76170499
https://www.tam-tam.co.jp/tipsnote/javascript/post9911.html
これでできる気になりました.
選択肢
(1) サーバなし VS あり
SPAでルーティングするためには, サーバなしでいける#タグと,サーバが必要だけどURL書き換えてリダイレクトする方法がある. どちらにするか.
(2) # VS ?
サーバなしで行く場合,#タグだけでなく, ?を使ってGETのパラメータを設定することもできる.どう使い分けるか.
試行
(1) NginxでURL書き換えてリダイレクトすれば, #タグなしでGit Hubみたいなページを作れるんじゃないかと思っていました. でも, Git Hubのgh-pagesで公開するにはサーバは使えない.
そんな時, 上の記事で#タグの存在を知って, サーバなしでできると分かったので, 迷わず#タグを選択しました.
サーバでURL書き換える方法は, きっとこういる感じに書けばいけると思います.
/jsnoteで来たリクエストを/にリダイレクトするサンプル(/index.htmlが呼ばれる)
location / {
root /usr/share/nginx/html;
index index.html;
}
location /jsnote {
rewrite /jsnote/(.*) / last;
}
(2) # VS ?
今回, #タグをつけると, #以下を無視してHTTPリクエストを送ると知りました. でも, それってGETのパラメータを指定する ? でも同じではないかと思いました.下の二つは, SPAでルーティングするときに何が違うのでしょうか.
localhost/#draw=1
localhost/?draw=1
2通りを試行してみた結果, 次のことが分かりました.
- #の場合
JavaScriptで動的に#以下を書き換えてもページがリロードされることはなく, popstateに登録したイベントだけが実行される. - ?の場合
JavaScriptで動的に?以下を書き換えるとページ全体がリロードされる. パラメータ変えるたびにリロードで画面がちらつく. GETのパラメータはHTTPリクエストとして送られるのだから, 当然かもしれない.
どちらでも同じページにはいけますが, やはり?はGETとしてパラメータをサーバに送る用途で使うと再確認し, #の方が画面がちらつかないし速いので#を選択しました.
実践
#以下のパラメータを扱うために4つの関数を作りました.
(1)parseParam: パラメータをパースしてMapオブジェクトに入れる.
(2)convertMapToHashString: パラメータを#タグようの文字列に戻す. (3)と(4)で使用.
(3)addParamToHash: パラメータを追加して#タグを書き換える.
(4)addParamToHash: パラメータを削除して#タグを書き換える.
const parseParam = (hash)=>{
if(hash){
const param = hash.split("&")
.reduce((pre,current)=>{
const item = current.split("=");
const key = item[0]
const value = item[1]
pre.set(item[0],isNaN(value)? value: parseFloat(value));
return pre;
},new Map());
return param
}
else {
return new Map();
}
};
const convertMapToHashString = (map)=>{
let paramArray = [];
for(let [k,v] of map){
paramArray.push(k+"="+v);
}
const paramString = paramArray.join("&");
//console.log(paramString);
return paramString
}
const addParamToHash = (key,value)=>{
const param = parseParam(location.hash.slice(1));
param.set(key,value);
location.hash = convertMapToHashString(param)
};
const removeParamFromHash = (key) =>{
const param = parseParam(location.hash.slice(1));
if(param.has(key)){
param.delete(key)
location.hash = convertMapToHashString(param)
return true;
}
else {
return false;
}
};
使い方
parseParamを使ってparamにパラメータを代入する.
const param = parseParam(location.hash.slice(1));
ユーザがパラメータを変えたときに,
addParamToHash("drawCheckBox",1);
もしくは,
removeParamToHash("drawCheckBox",1);
という感じで#タグを書き換える.
戻るボタンに対応
戻るボタンに対応するのが大変でした. リロードボタンを押してから, 戻るボタンを押すと挙動不審になりました.
そこで, この記事を読んでhistory APIを勉強させてもらいました.
わかったこと
(1)パラメータ操作する内容は関数にしておいて, 最初に全部popstateに登録すると,リロード&戻るボタンしてもそれっぽく動く.
const jsnoteInitialize = ()=>{//やりたいこと全部書く
keyBinding();
fontSize();
getCode();
checkBox();
};
jsnoteInitialize();
window.onpopstate = jsnoteInitialize;
(2)以降, JavaScriptで動的に#タグを書き換えると, popstateで登録した関数が呼ばれるので, #タグを書き換える前に, 個別の関数をpopstateに登録するとよい. 自分で関数を呼ぶと2回呼ばれることになるから, 自分では呼ばずhashchangeイベントに任せる.
window.onpopstate = keyBinding; //hashの変更前にpopstateに関数を登録
addParamToHash("keyBinding",keyElem.selectedIndex); //hashchangeで自動的にkeyBindingが呼ばれる
まとめ
#タグにパラメータを入れて, 処理内容をpopstateに登録して, 動的に#タグをJavaScriptで書き換えれば, ルーティングできる.