6日目のゴールと今日やること
6日目のテーマは
「TabRouter を“アプリ全体の中でどう振る舞わせるか”を考える」ことです。
ここまでであなたは、
URL hash を使ったタブ切り替え、履歴対応、再読み込み復元、
TabRouter クラス、タブごとのコントローラ、
遷移中フラグやローディング状態まで扱えるようになりました。
今日はそこから一歩引いて、
「この TabRouter がアプリ全体の中でどう位置づけられるか」
を意識しながら、
- 画面遷移と“グローバルな UI”(モーダル・通知など)の関係
- 画面遷移と“アプリ状態”(ログイン状態など)の関係
- 画面遷移と“URL 設計”の関係
を、コードとイメージでつなげていきます。
画面遷移と「グローバル UI」の関係を考える
タブだけが UI じゃない世界
現実のアプリでは、
タブ以外にもいろいろな UI が動いています。
画面上部にグローバルな通知バーがある。
モーダルがタブの上に出てくる。
トースト通知が画面の端に出る。
ここで大事なのは、
「タブが変わっても消えない UI」との関係 です。
例えば、
「設定タブでエラーが出たときに、画面上部に通知を出す」
というケースを考えてみましょう。
TabRouter と通知コンポーネントを“ゆるくつなぐ”
通知コンポーネントを仮定する
まず、シンプルな通知コンポーネントを想像します。
class Notifier {
show(message, type = "info") {
const box = document.querySelector("#notice");
box.textContent = message;
box.dataset.type = type;
box.classList.add("is-visible");
setTimeout(() => {
box.classList.remove("is-visible");
}, 3000);
}
}
JavaScriptこれは「どの画面でも使える」グローバルな UI です。
TabRouter の中で直接使わない、がポイント
やりがちなのは、
TabRouter の中で notifier.show(...) を呼んでしまうことです。
でもそれをやると、
TabRouter が「通知の存在」を知ってしまい、
責務が重くなります。
代わりに、
タブコントローラ側で通知を使う 形にします。
タブコントローラから通知を呼ぶ例
設定タブでエラーが出たとき
const notifier = new Notifier();
const controllers = {
settings: {
async onEnter(from, to) {
try {
const data = await fetch("/api/settings").then(res => res.json());
// 設定画面に反映する処理…
} catch (e) {
notifier.show("設定の読み込みに失敗しました", "error");
}
}
}
};
JavaScriptここでの関係はこうです。
TabRouter は「settings に入った」という事実だけを管理する。
settings のコントローラは「入ったときに何をするか」を決める。
通知は「エラーが起きたら見せる」だけを担当する。
それぞれが自分の責務だけを持っていて、
お互いを“強く知らない”状態になっています。
これが、
アプリ全体を壊れにくくする設計の感覚です。
画面遷移と「アプリ状態(ログインなど)」の関係
ログインしていないと入れないタブ
よくある要件です。
プロフィールタブはログインしていないと見せたくない。
設定タブも同様。
ホームだけは誰でも見られる。
このとき、
「ログインしているかどうか」というのは
アプリ全体の状態 です。
TabRouter はそれをどう扱うべきか、を考えます。
beforeChange で「アプリ状態」を見る
ログイン状態を持つ
ここでは、超シンプルにフラグで持ちます。
let isLoggedIn = false;
JavaScriptログインボタンを押したら true にする、
というイメージです。
beforeChange でチェックする
const router = new TabRouter(nav, views, {
defaultTab: "home",
beforeChange: (from, to) => {
const needLoginTabs = ["profile", "settings"];
if (needLoginTabs.includes(to) && !isLoggedIn) {
window.alert("このタブを見るにはログインが必要です");
return false;
}
return true;
}
});
JavaScriptここでのポイントは、
TabRouter 自体は「ログインの仕組み」を知らない。
ただ「beforeChange で止められたら遷移しない」というルールだけ守る。
ログインしているかどうかは、
アプリ側(外側)が管理している。
TabRouter は「画面遷移のルール」を提供し、
アプリ側は「そのルールをどう使うか」を決める。
この関係がとても大事です。
画面遷移と「URL 設計」の関係をもう一段深く見る
今までは「タブ名 = hash」だった
ここまでは、#home#profile#settings
のように、
「タブ名そのものを hash にする」設計でした。
これはシンプルで分かりやすい反面、
アプリが大きくなると少し物足りなくなります。
例えば、
#settings/profile#settings/notification
のように、
「設定タブの中のどの画面か」まで表現したくなることがあります。
hash を「構造化された情報」として扱う発想
文字列ではなく“意味のあるパーツ”として見る
例えば、#settings/profile という hash を見たときに、
"settings/profile" という文字列としてではなく、["settings", "profile"] という配列として扱う、
という発想です。
function parseHash() {
const hash = window.location.hash.replace("#", ""); // "settings/profile"
if (!hash) return [];
return hash.split("/"); // ["settings", "profile"]
}
JavaScriptこの配列を使って、
先頭の "settings" は「大きなタブ」
2つ目の "profile" は「設定タブの中のサブタブ」
というように解釈できます。
外側 TabRouter と内側 TabRouter の連携イメージ
外側の TabRouter
外側は「大きな画面」を担当します。
const outerRouter = new TabRouter(outerNav, outerViews, {
defaultTab: "home",
parseHash: () => {
const parts = parseHash();
return parts[0] || "home";
}
});
JavaScript内側の TabRouter(設定タブの中)
設定タブの中だけで動く TabRouter を作ります。
const innerRouter = new TabRouter(innerNav, innerViews, {
defaultTab: "profile",
parseHash: () => {
const parts = parseHash();
if (parts[0] !== "settings") return "profile";
return parts[1] || "profile";
}
});
JavaScriptここでは少し抽象的な話をしていますが、
大事なのは、
URL hash を「アプリ全体の状態を表すもの」として設計する。
TabRouter は、その一部を解釈して自分の責務を果たす。
という視点です。
6日目では、
「こういう設計もできるんだ」という
“地図の見え方”を持ってもらえれば十分です。
6日目の全体像を言葉でなぞる
ここまでを、コードではなく「関係性」で整理します。
TabRouter は、
「今どのタブか」「どう遷移するか」を管理する存在。
通知コンポーネントは、
「メッセージをユーザーに見せる」だけを担当する。
ログイン状態は、
アプリ全体の状態として外側で管理される。
beforeChange / afterChange / onEnter / onLeave は、
「画面遷移」と「アプリの他の部分」を
ゆるくつなぐ“接点”として機能する。
URL hash は、
「アプリの状態を URL 上に表現するための窓」であり、
単なる文字列ではなく「意味のあるパーツ」に分解してもよい。
TabRouter は、
その hash の一部を解釈して、
自分の担当する画面遷移だけをきちんと面倒見る。
こうして、
一つのクラスが全部を抱え込むのではなく、
役割ごとに分かれたパーツが
お互いを“強く知らずに”連携する。
これが、中級レベルの「画面遷移管理」の感覚です。
今日いちばん深く理解してほしいこと
6日目の本質は、
「TabRouter は“アプリの一員”であって、“全部”ではない」
ということです。
タブを切り替える。
履歴に対応する。
再読み込みで復元する。
それだけでも十分すごいのに、
そこにさらに、
通知と連携する。
ログイン状態と連携する。
URL 設計と連携する。
という“アプリ全体の文脈”が乗ってくる。
あなたはもう、
「タブ UI を作れる人」ではなく、
「画面遷移をアプリの一部として設計できる人」 にかなり近いところまで来ています。
7日目では、
ここまでの TabRouter を一度まとめて、
「自分の言葉で説明できるか」を確認する回にしていきましょう。


