3日目のゴールと今日やること
3日目のテーマは
「タブ切り替えに“画面遷移らしさ”を足して、アプリっぽい振る舞いにする」ことです。
1日目で「hash を状態として扱う」
2日目で「TabRouter クラスとしてまとめる」
ところまで来ました。
3日目では、そこに一歩踏み込んで、
- タブ遷移の前後に「処理(フック)」を差し込む
- 「このタブには行っていいか?」を判定する
- 存在しない hash が来たときの扱いを決める
といった「画面遷移管理らしさ」を入れていきます。
画面遷移には「前後の処理」がつきものだと知る
単なるタブ切り替えと、画面遷移の違い
ただのタブ切り替えは
「見た目を変えるだけ」です。
でも、アプリの画面遷移はたいていこうなります。
- 遷移前に「保存してない変更があるけどいい?」と確認したい
- 遷移後に「データを読み込む」「初期化する」などをしたい
- 特定のタブはログインしていないと入れないようにしたい
つまり、
「タブが変わる前後で何かしたい」
というニーズが必ず出てきます。
これをクラスの中にうまく組み込むのが、今日のメインテーマです。
TabRouter に「フック」を追加する発想
フックとは何か
フックは簡単に言うと
「ここで外部の処理を呼び出すよ」という“引っかかり”です。
今回やりたいのは、例えばこんな感じ。
- タブが変わる前に
beforeChangeを呼ぶ - タブが変わった後に
afterChangeを呼ぶ
これを TabRouter に組み込むと、
外側からこう書けるようになります。
const router = new TabRouter(nav, views, {
defaultTab: "home",
beforeChange: (from, to) => { ... },
afterChange: (from, to) => { ... }
});
JavaScript「画面遷移管理」という言葉が、
ここで一気に“本物っぽく”なります。
beforeChange で「行っていいか?」を判定する
仕様を決める
beforeChange は、
「今のタブ」と「次のタブ」を受け取って、
遷移してよければ true、ダメなら false を返す、
という仕様にします。
beforeChange: (from, to) => {
// true を返せば遷移続行
// false を返せばキャンセル
}
JavaScriptTabRouter に組み込む
コンストラクタで受け取ります。
constructor(navElement, viewElements, options = {}) {
this.beforeChange = options.beforeChange || null;
this.afterChange = options.afterChange || null;
}
JavaScriptそして、タブを変える前に呼びます。
changeTab(nextTab) {
const prevTab = this.currentTab;
if (this.beforeChange) {
const ok = this.beforeChange(prevTab, nextTab);
if (!ok) {
// hash を元に戻す
window.location.hash = prevTab;
return;
}
}
this.currentTab = nextTab;
this.render();
if (this.afterChange) {
this.afterChange(prevTab, nextTab);
}
}
JavaScriptここで重要なのは、
「状態を変える前に、必ず beforeChange を通す」
というルールを作っていることです。
hash とクリックの両方から changeTab を通す
クリック時
handleTabClick(event) {
event.preventDefault();
const tab = event.currentTarget;
const tabName = tab.dataset.tab;
if (!tabName) return;
if (this.currentTab === tabName) return;
window.location.hash = tabName;
// 実際の状態変更は hashchange 側に任せる
}
JavaScripthashchange 時
handleHashChange() {
const tabName = this.getTabFromHash() || this.defaultTab;
if (this.currentTab === tabName) return;
this.changeTab(tabName);
}
JavaScriptポイントは、
「状態変更の入口を changeTab に一本化する」
ことです。
クリックでも、戻る / 進むでも、
最終的には changeTab を通る。
だからこそ、
beforeChange / afterChange をそこに書けば、
すべての遷移に対して一貫して動きます。
存在しない hash が来たときの扱いを決める
問題
URL に #unknown のような
存在しないタブ名が付いていたらどうするか。
- そのまま何も表示されない
- 変な状態になる
というのは避けたい。
解決策:有効なタブ名かどうかをチェックする
まず、有効なタブ名の一覧を持ちます。
constructor(navElement, viewElements, options = {}) {
this.navElement = navElement;
this.viewElements = Array.from(viewElements);
this.validTabs = this.viewElements.map(v => v.dataset.view);
this.defaultTab = options.defaultTab || this.validTabs[0];
...
}
JavaScriptそして、hash から取ったタブ名が
validTabs に含まれているかをチェックします。
normalizeTabName(name) {
if (this.validTabs.includes(name)) {
return name;
}
return this.defaultTab;
}
JavaScripthashchange ではこう使います。
handleHashChange() {
const raw = this.getTabFromHash();
const tabName = this.normalizeTabName(raw || this.defaultTab);
if (this.currentTab === tabName) return;
this.changeTab(tabName);
}
JavaScriptこれで、
- 変な hash が来ても安全なタブに戻す
- URL を手で書き換えられても壊れない
という「ルーターらしさ」が出てきます。
具体例:保存してない変更があるときに遷移を止める
beforeChange の活用例
例えば「プロフィール編集」タブで
フォームに入力している途中に
別タブに移動しようとしたら確認したい、
というケースを考えます。
let isDirty = false;
const form = document.querySelector("#profileForm");
form.addEventListener("input", () => {
isDirty = true;
});
const router = new TabRouter(nav, views, {
defaultTab: "home",
beforeChange: (from, to) => {
if (from === "profile" && isDirty) {
const ok = window.confirm("保存していない変更があります。移動してもいいですか?");
if (!ok) return false;
isDirty = false;
}
return true;
},
afterChange: (from, to) => {
console.log(`タブが ${from} から ${to} に変わりました`);
}
});
JavaScriptここでやっていることはシンプルです。
プロフィールタブから離れるときだけ、
「本当にいい?」と聞く。
キャンセルされたら false を返して遷移を止める。
TabRouter 側は
「true なら進む、false ならやめる」
というルールを守るだけです。
3日目の全体像を言葉でなぞる
ここまでを、コードではなく「流れ」で整理します。
TabRouter は、
「今どのタブか」という状態(currentTab)を持っている。
表示は render が currentTab に合わせて更新する。
タブが変わるときは、
必ず changeTab(nextTab) を通る。
changeTab は、
今のタブと次のタブを beforeChange に渡して聞く。
ダメと言われたら、状態を変えずに終わる。
OK なら currentTab を更新して render を呼ぶ。
最後に afterChange を呼ぶ。
クリックでも、戻る / 進むでも、
最終的には changeTab に集約される。
URL の hash は
「状態を外に見せるための表現」であり、
状態そのものは currentTab としてクラスが持っている。
存在しない hash が来たら、
validTabs を見て安全なタブに戻す。
これが「画面遷移管理」という言葉の中身です。
今日いちばん深く理解してほしいこと
3日目の本質は、
「画面遷移は“状態を変える前に一度立ち止まる”仕組みを持つと、一気にアプリっぽくなる」
ということです。
ただタブを切り替えるのではなく、
本当に行っていい?(beforeChange)
行ったあとに何をする?(afterChange)
そのタブはそもそも存在する?(normalizeTabName)
こういう問いをコードの中に埋め込んでいくと、
「ただのタブ UI」から
「ちゃんと設計された画面遷移」に変わります。
4日目では、
この TabRouter を「ネストしたタブ」や「モーダルとの連携」など、
もう一段アプリ寄りの使い方に広げていきます。


