Vue Router (Vue.js その2)

Vue Router(公式プラグイン)を使うとシングルページアプリケーションを簡単に作成できます

シングルページアプリケーション(SPA)

シングルページアプリケーションでは最初にスクリプトを読み込み、その後はスクリプトの処理だけで、ページ遷移もおこないます(1枚のHTMLファイルだけで処理します)
ページ遷移がスムーズです
しかし、シングルページアプリケーションでは、ブラウザのJavaScriptで内容を書き換えているので、現状では検索エンジン(クローラー)に情報を充分に伝えることができません

Vue Routerを使わない場合の、ルーティングの実装例(ドキュメントより)

コードを見る

<script setup>
import { ref, computed } from 'vue'
import Home from './Home.vue'
import About from './About.vue'
import NotFound from './NotFound.vue'

const routes = {
  '/': Home,
  '/about': About
}

const currentPath = ref(window.location.hash)

window.addEventListener('hashchange', () => {
  currentPath.value = window.location.hash
})

const currentView = computed(() => {
  return routes[currentPath.value.slice(1) || '/'] || NotFound
})
</script>

<template>
  <a href="#/">Home</a> |
  <a href="#/about">About</a> |
  <a href="#/non-existent-path">Broken Link</a>
  <component :is="currentView" />
</template>

Vue Routerが、コンポーネントルートの紐付をしてくれます

目次
  1. Vue Routerの設定
  2. router-linkとrouter-viewコンポーネント
  3. $routerと$route
  4. ナビゲーションガード
  5. デプロイ時の設定について

Vue Routerの設定

Vue 3ではVue Router v4.xが必要です

npm install vue-router@4

v3(Vue 2の場合)とv4(Vue 3の場合)の比較
「historyモード」を追加しています(URLにハッシュ「#」をつけません)
*デフォルトは「hashモード」です(ハッシュを使ってページ遷移します)

Vue Router v3

import Vue from 'vue'
import VueRouter from "vue-router";
//コンポーネントのimportは省略
Vue.use(VueRouter)

//コンポーネントとルートを紐付
const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About },
]

const router = new VueRouter({
  mode: 'history'
  routes
})

const app = new Vue({
  router
}).$mount('#app')

Vue Router v4

createRouterは、Vueアプリで使用できるルーターインスタンス(router)を作成します

import { createApp } from 'vue'
import { createRouter, createWebHistory } from "vue-router";
//コンポーネントのimportは省略

const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About },
]
//ルーターインスタンス
const router = VueRouter.createRouter({
  history: createWebHistory(),
  routes,
})
//useメソッドの引数にルーターインスタンスをわたしてvue-routerプラグインをアプリに追加
createApp(App).use(router).mount('#app');

router-linkとrouter-viewコンポーネント

<router-link>
リンクを作成します(デフォルトはaタグです)
プロパティ(Props)の例
to:はリンクする対象のルートを表します
tag:タグの変更
<router-view />
ここに、URLに対応するコンポーネントが表示されます

<template>
     <router-link to="/">HOME</router-link>
     <router-link to="/about">ABOUT</router-link>
     <router-view />
</template>

アクティブなリンクに「class」がつきます

router-link-active:リンクがアクティブなときにつくclass
router-link-exact-active:リンクが正確にアクティブなときにつくclass
*現在のパスから始まるとリンクがアクティブです
to=”/”は全てのルートに対してアクティブです

ルートに名前をつけることができます
名前を付けたルートにリンクするには、 <router-link>のtoプロパティにv-bindを使って、オブジェクトを渡します

 routes: [
    {
      path: '/about',
      name: 'about',
      component: User
    }
  ]
//テンプレート
<router-link :to="{ name: 'about'}">ABOUT</router-link>

*URLパラメータが必要な場合は、params: { パラメータ名: 値 }を追記します
パラメータ(params)を使うときはnameを使います

「childrenオプション」を使うと、ネストされた<router-view />を作ることができます

 routes: [
    {
      path: '/user/:id',
      component: User,
      children: [
        {
          // /user/:id/profile がマッチした時に
          // UserProfileはUserコンポーネントの <router-view>で描画されます
          path: 'profile',
          component: UserProfile
        },
        {
          // /user/:id/posts がマッチした時に
          // UserPostsはUserUserコンポーネントの<router-view>で描画されます
          path: 'posts',
          component: UserPosts
        }
      ]
    }
  ]

ネストすることなく同時に複数の<router-view>を表示するには
<router-view name="" />(名前付きビュー)を使います
名前を持たない <router-view />はのnameはdefaultです

<router-view name="sidebar" />
<router-view />
//components(sをつけた複数形です)
routes: [
    {
      path: '/',
      components: {
        default: Main,
        sidebar: Sidebar 
      }
    }
  ]

<router-view>は動的コンポーネントなので、 コンポーネントを使うのと同じ方法でトランジションを適用できます

<transition>
  <router-view />
</transition>

$routerと$route

$routerscriptからURLを切り替えるときに使います)
Vueインスタンスの内部では、$routerとしてルーターインスタンスにアクセスできます
router-link :to="…"をクリックは router.push(…)と同じです
*ちなみに、JavaScriptのHistory操作と同じgoやreplaceなどに対応したメソッドもあります
https://v3.router.vuejs.org/ja/guide/essentials/navigation.html

<template>
<router-link to="/about">ABOUT</router-link>
//scriptからURLを切り替える
<button @click="toAbout">aboutページに行く</button>
</template>

<script>
export default {
    methods: {
        toAbout() {
       //スクリプトではthisが必要
          this.$router.push('/about')
        }
    }
};
 </script>

$route
route(ルートオブジェクト)で現在のアクティブなルートの状態を表しています
プロパティ
path:現在のルートのパスに等しい文字列
query:クエリ文字列の key/value ペアを保持するオブジェクト
例えば /foo?user=1 というパスの場合、$route.query.user == 1です
クエリがない場合は、空オブジェクトです
params動的セグメントとスターセグメントの パラメータ名/valueペアを保持するオブジェクト
パラメーターがない場合、空オブジェクトです

ちなみにsetup関数内(Composition API)でvue-routerを使う場合
テンプレート内では今までどおり、$router、$routeでアクセスできます
setup関数内では

import { useRoute, useRouter } from 'vue-router'
const router = useRouter()
const route = useRoute()

//ナビゲーションガード
import { onBeforeRouteLeave, onBeforeRouteUpdate, } from 'vue-router'
onBeforeRouteLeave((to, from) => {
      // 
 })
onBeforeRouteUpdate((to, from) => {
      // 
})

動的なURLパラメータ名の前にコロン「:」をつけます (例:/user/:id
例では:/user/の後に何らかのパスがあるときはUserコンポーネントが呼ばれます

const User = {
//$route.params.idで 値を取得 (/user/123なら 123)
  template: '<div>{{ $route.params.id }}</div>'
}
const router = new VueRouter({
  routes: [
     { 
      path: '/user/:id', 
      component: User 
     }
  ]
})

上のように、コンポーネントで$routeを直接利用した場合、コンポーネントとルートの間に密結合が生まれ、コンポーネントが特定のURLでしか使用できないなど柔軟性が制限されます
通常「propsオプション」を使ってルート情報をコンポーネントに渡します

props: trueを設定すると、route.params がコンポーネントのprops(プロパティ)として設定されます

const User = {
  props: ['id'],
  template: '<div>User {{ id }}</div>'
}

const router = new VueRouter({
  routes: [
    { 
      path: '/user/:id', 
      component: User, 
      props: true 
    },
 
  // 余談:名前付きビューによるルートは、名前付きビューごとにpropsオプションを定義します
   {
      path: '/',
     components: {
      default: Main, 
      sidebar: Sidebar 
      },
      props: { 
       default: true, 
       sidebar: true
     }
  }
 ]
})

propsがオブジェクトの場合、コンポーネントプロパティとしてそのまま設定されます

const Page = {
  props:['propA', 'propB'],
  template: '<div>  {{ propA }} {{ propB }} </div>'
}

{
  path: '/page/:id',
  name: 'page',
  component: Page,
  props: {
    propA: 'AAA',
    propB: 'BBB',
  }
}

プロパティを返す関数を作成できます
パラメータは文字列なので数値型へキャスト

const User = {
  props: {
    id: Number
   },
  template: '<div>User {{ id }}</div>'
}
{
  path: '/page/:id',
  name: 'page',
  component: Page,
  props: route => ({
//URLパラメータは文字列なので数値型へキャスト
    id: Number(route.params.id) 。
  })
}

パラメータの変更(同じコンポーネントを使っている)では、コンポーネントインスタンスが再利用されます(ライフサイクルフックは呼び出せません)
副作用が必要な場合は、watchかナビゲーションガード(beforeRouteUpdate)を使用します:

watch: {
    $route(to, from) {
      // ルートの変更を検知してする処理
    }

//または

beforeRouteUpdate(to, from, next) {
    // ルート変更に反応してする処理
    next() // next()は必須
  }

ナビゲーションガード

ページ遷移する間になんらかの処理を実行します

  • to:次にナビゲーションされる対象
  • from:ナビゲーションされる前の現在のルート
  • next():次のフックに移動します
  • next(false):現在のナビゲーションを中止します

グローバル:すべてのルートが対象(ログイン認証などに利用)

router.beforeEach((to, from, next) => {
  if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
  else next()
})

ルート単位:ルート設定オブジェクトにbeforeEnterを定義します

routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]

コンポーネント内:ルータ設定に渡されるコンポーネントの内側でルートナビゲーションガードを直接定義します

const Foo = {
  template: `...`,
  beforeRouteEnter(to, from, next) {
    // ルートが確立する前に呼ばれ、thisでのこのコンポーネントへのアクセスはできません
  },
  beforeRouteUpdate(to, from, next) {
    // ルートが変更されたときに呼び出され、thisでコンポーネントインスタンスにアクセスできます
  },
  beforeRouteLeave(to, from, next) {
    // ルートがナビゲーションから離れていく時に呼ばれ、thisでコンポーネントインスタンスにアクセスできます
  }
}

デプロイ時の設定について

「historyモード」の場合、本番環境ではサーバー側でどのリクエストに対しても全て「index.htmlファイル」を返却する設定が必要です

ちなみにNetlifyでは/publicの配下に _redirectsファイルを作成します

/*    /index.html   200

全てのパスが「index.htmlファイル」を返却するため、404エラーをサーバーがレポートしません
Vueアプリケーション内で404ページを表示するには、 catchAllを使用して、指定したパス以外は「NotFoundコンポーネント」を設定します

  routes: [
   // 一番最後に マッチしなかったルートの全て
    { path: '/:catchAll(.*)', component: NotFound } 
   ]

「Vue CLI」のデフォルトの設定では、アプリがドメインのルートにデプロイされることを前提としています
サブディレクトリにデプロイする場合(例:https://hoge.com/my-app/)publicPathを’/my-app/’にする必要があります
vue.config.jsファイルを作成します

module.exports = {
 publicPath: '/my-app/'
}