JavaScript | 1 日 120 分 × 7 日アプリ学習:SPA風タブ切り替えアプリ

Web APP JavaScript
スポンサーリンク

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 を返せばキャンセル
}
JavaScript

TabRouter に組み込む

コンストラクタで受け取ります。

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 側に任せる
}
JavaScript

hashchange 時

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;
}
JavaScript

hashchange ではこう使います。

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 を「ネストしたタブ」や「モーダルとの連携」など、
もう一段アプリ寄りの使い方に広げていきます。

タイトルとURLをコピーしました