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が、コンポーネントとルートの紐付をしてくれます
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
$router
(scriptから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/'
}