Vue の基本的な使い方 (4) Vue Router ルーティング
以下は Vue のルーティングと Vue Router バージョン 4.x (この時点では 4.1.5)の基本的な使い方についての解説のような覚書です。使用している Vue のバージョンは 3.2.38 です。
Vue Router の使い方は Vite で作成したデフォルトのプロジェクトを使って解説しています。
関連ページ
- Vue の基本的な使い方 (1) Options API
- Vue の基本的な使い方 (2) Composition API
- Vue の基本的な使い方 (3) Vite と SFC 単一ファイルコンポーネント
- Vue の基本的な使い方 (5) Pinia を使って状態管理
- Vue の基本的な使い方 (6) Vue3 で簡単な To-Do アプリを色々な方法で作成
- Vue の基本的な使い方 (7) Vue Router と Pinia を使った簡単なアプリの作成
作成日:2022年11月10日
ルーティング
Vue.js には画面遷移の手段として、リクエストされた URL に応じてコンポーネントを選択して表示するルーティング(Routing)という仕組みが用意されています。
ルーティングを利用することで、初回で単一の Web ページのみを読み込み、動的にコンテンツ(コンポーネント)を切り替えることができます。このような複数の機能を単一のページで構成するアプリを Single Page Application(SPA) と呼びます。
SPA ではルーティングはクライアントサイド(JavaScript)で行われ、History API や hashchange イベントなどのブラウザー API を使用して、アプリケーションの表示の切り替えを管理します。
シンプルなルーティング
ハッシュ(#)を使った単純なルーティングの場合などでは、動的コンポーネントを使って、現在のコンポーネントの状態を変更することができます。
my-vite-project ├── index.html ├── src │ ├── App.vue │ ├── components │ │ ├── About.vue //http://127.0.0.1:5173/#/about │ │ ├── Home.vue //http://127.0.0.1:5173/#/ │ │ └── NotFound.vue //http://127.0.0.1:5173/#/non-existent-path(上記以外) │ └── main.js └── vite.config.js
以下は上記のファイル構成で、URL の # の部分が変化したときに発生する hashchange イベント を利用してコンポーネントを切り替える動的コンポーネントを使った単純なルーティングの例です。
リアクティブな変数 currentPath には、URL のフラグメント(# 記号からの部分)が入っています。
テンプレートのリンク(a 要素)をクリックすると hashchange のイベントリスナーにより currentPath の value プロパティがリンク先の URL のフラグメントの値で更新されます。
算出プロパティの currentView は computed メソッドで routes と currentPath.value からコンポーネント名を生成して返します。例えば routes['/about'] なら About になります。
そしてテンプレートの component 要素(34行目)の is 属性に指定されている currentView が更新されることでコンポーネントを動的に切り替えています。
また、初期状態では currentPath.value の値(window.location.hash)は空なので、26行目では routes[currentPath.value.slice(1) || '/'] として初期状態では currentView が Home になるようにしています。
<script setup>
import { ref, computed } from 'vue';
import Home from './components/Home.vue';
import About from './components/About.vue';
import NotFound from './components/NotFound.vue';
//パスとコンポーネント名からなるルーティング情報のオブジェクトを定義
const routes = {
'/': Home,
'/about': About
};
//URL のフラグメント(# 記号からの部分)を取得してリアクティブに
const currentPath = ref(window.location.hash);
//hashchange のイベントリスナー
window.addEventListener('hashchange', () => {
//hashchange イベントでフラグメントを取得して currentPath の value プロパティを更新
currentPath.value = window.location.hash;
//console.log(currentPath.value); //About のリンクをクリックすると #/about と出力
});
//currentPath.value と routes からコンポーネント名を生成
const currentView = computed(() => {
//currentPath.value.slice(1) は「#」を除いた値(該当しなければ NotFound)
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>
<template> <h1>About</h1> </template>
<template> <h1>Home</h1> </template>
<template> <h1>404</h1> </template>
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
<body> <div id="app"></div> <script type="module" src="/src/main.js"></script> </body>
<div id="app" data-v-app=""> <a href="#/">Home</a> | <a href="#/about">About</a> | <a href="#/non-existent-path">Broken Link</a> <h1>Home</h1> </div>
上記のようなハッシュベースの単純なルーティングであればルーティングのライブラリは不要ですが、構成が複雑になったり、URL の操作が必要な場合などではルーティング機能を提供するライブラリーを使用するのが簡単です。
Vue Router 概要
Vue は SPA の構築にルーティング機能を提供するライブラリーとして、Vue が公式にサポートする Vue Router ライブラリーを推奨しています。
以下で扱っている Vue Router のバージョンは 4.1.5、Vue のバージョンは 3.2.38、Vite のバージョンは 3.0.9 です。
- Vue Router 4 ガイド(英語。日本語は現時点ではまだないようです)
- Vue Router 3 ガイド(日本語)
インストール
Vue Router は CDN で読み込んで利用することも Vite などを使ってプロジェクトを構築して利用することもできます。
CDN で利用
Vue Router はダウンロードしたり CDN で読み込んで利用することができます。Unpkg.com の以下のリンクは常に vue-router v4 の最新バージョンを指しています。
https://unpkg.com/vue-router@4
https://unpkg.com/vue-router@4.1.5/dist/vue-router.global.js のように @ 以降に特定のバージョンを指定して使用することもできます。
ダウンロードしたり CDN で読み込んで利用する場合は、Vue の後に Vue Router を読み込むと自動的にインストールされます。
<!-- Vue の読み込み --> <script src="https://unpkg.com/vue@3"></script> <!-- Vue Router の読み込み --> <script src="https://unpkg.com/vue-router@4"></script>
以下は Vue と Vue Router を CDN で読み込んで使用する例です(Getting Started)。
テンプレートでは、リンクは <a> タグの代わりに <router-link> を使います。定義したルートとマッチしたコンポーネントが <router-view> へ描画されます。
スクリプトでは、createRouter() に routes オプション(ルートの定義)と history オプションを指定してルーターを生成します。
そして Vue のインスタンスを生成し、use() メソッドに生成したルーターを渡して有効化し、Vue のインスタンスをマウントします。
<body>
<div id="app">
<nav>
<!-- ナビゲーションのリンクに router-link コンポーネントを使います -->
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
</nav>
<!-- ルートとマッチしたコンポーネントが以下の router-view コンポーネントへ描画されます -->
<router-view></router-view>
</div>
<!-- Vue の読み込み -->
<script src="//unpkg.com/vue@3"></script>
<!-- Vue Router の読み込み -->
<script src="//unpkg.com/vue-router@4"></script>
<script>
//1.ページを構成するコンポーネントを定義(他のファイルからインポートすることもできます)
const Home = {
template: `
<div class="main">
<h1>This is Home!</h1>
</div>
`
}
const About = {
template: `
<div class="main">
<h1>This is an About page</h1>
</div>
`
}
//2.ルートを定義(3.で routes に指定)
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
]
//3.ルーターを作成する際に、2.で定義したルート(routes)を渡します
const router = VueRouter.createRouter({
//4.history オプションを指定(ここでは簡単にするためにハッシュ history を使用)
history: VueRouter.createWebHashHistory(),
routes, // routes オプションを指定(routes: routes の短縮表記)
})
//5.Vue のインスタンスを生成
const app = Vue.createApp({})
//6.Vue にルーターをインストールして有効化
app.use(router)
//7.Vue のインスタンスをマウント
app.mount('#app')
</script>
</body>
router-link(RouterLink)コンポーネントは、ナビゲーションを有効にするための Vue Router のコンポーネントで、router-view(RouterView)コンポーネントは現在のパスに対してマッチしたコンポーネントを描画する Vue Router のコンポーネントです。
</router-link> のように DOM テンプレートの中ではコンポーネント名はケバブケースで記述しますが、単一ファイルコンポーネントのテンプレート内では、コンポーネント名はパスカルケースにすることが推奨されています。
<div id="app" data-v-app="">
<nav>
<a href="#/" class="router-link-active router-link-exact-active" aria-current="page">Home</a>
<a href="#/about" class="">About</a>
</nav>
<div class="main">
<h1>This is Home!</h1>
</div>
</div>
上記のサンプルは、例えば以下のように表示されます。URL の最後は「router-sample.html#/」のようにハッシュが追加されています。また、以下ではブラウザの拡張機能 Vue Devtools も表示しています。
「About」のリンクをクリックすると、 URL の最後は「router-sample.html#/about」のようにハッシュの後に /about が 追加され、以下のように表示されます。
また、Vue Devtools には「Routes」のタブが追加されていて、ルートの情報などを確認できます。
Vite を利用
Vite で作成したプロジェクトに npm install vue-router@4 で Vue Router を追加できます。
以下では Vite を使って Vue Router をあらかじめ追加した Vue プロジェクトを作成します。
以下は Vue プロジェクトを生成(初期化)するコマンド npm init vue@latest(create-vue)で Vue Router を含む Vue のプロジェクトを新規作成する例です(npm create vue@latest でも同じ)。
途中で Add Vue Router for Single Page Application development? と聞かれた際に、 yes を選択して Vue Router を追加します。
プロジェクト生成ウィザードではデフォルトが No なのでそのまま return キーを押せば、その項目はインストールされません。
% npm init vue@latest return //npm init vue でプロジェクトを生成(初期化) Vue.js - The Progressive JavaScript Framework ✔ Project name: … my-router-project ✔ Add TypeScript? … No / Yes //そのまま return キーを押せば No(インストールされない) ✔ Add JSX Support? … No / Yes // 以下は矢印キーで yes を選択して return キーを押して Vue Router を追加 ✔ Add Vue Router for Single Page Application development? … No / Yes //→Yes ✔ Add Pinia for state management? … No / Yes ✔ Add Vitest for Unit Testing? … No / Yes ✔ Add Cypress for both Unit and End-to-End testing? … No / Yes ✔ Add ESLint for code quality? … No / Yes Scaffolding project in /Applications/MAMP/htdocs/vue/my-router-project2... Done. Now run: // 準備(Scaffolding)ができたので、続いて以下のコマンドを実行(後述) cd my-router-project npm install npm run dev
プロジェクトの準備(Scaffolding)が完了するとプロジェクト名と同じ名前のフォルダが、コマンドを実行したディレクトリの下に作成されます。
npm create vite@latest を利用する場合
または、npm create vite@latest(create-vite)でも同様にウィザードを起動して、Vue Router を追加した Vue プロジェクトを作成することもできます。※ 上記を実行した場合は不要です。
create-vite の場合、表示されるウィザードの
Select a frameworkでVueを選択? Select a variantでCustomize with create-vueを選択-
Add Vue Router for Single Page Application development?でYesを選択して
Vue Router を追加した Vue プロジェクトを生成します。
create-vue 同様、プロジェクトの準備(Scaffolding)が完了するとプロジェクト名と同じ名前のフォルダが、コマンドを実行したディレクトリの下に作成されます。
% npm create vite@latest return //npm create vite でプロジェクトを生成(初期化)
✔ Project name: … my-router-project
? Select a framework: › - Use arrow-keys. Return to submit.
Vanilla
❯ Vue //Vue を選択して return キーを押す
React
Preact
Lit
Svelte
Others
? Select a variant: › - Use arrow-keys. Return to submit.
JavaScript
TypeScript
❯ Customize with create-vue //これを選択して return キーを押す
Nuxt
// 必要なパッケージがある場合は以下のように聞かれるのでインストール
Need to install the following packages:
create-vue@3.3.4
Ok to proceed? (y) y // y を選択して return キーを押す
Vue.js - The Progressive JavaScript Framework
// 追加する必要があるものは矢印キーで Yes を選択します(この例では Vue Router のみを Yes に)
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
// 以下で yes を選択して Vue Router を追加
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit Testing? … No / Yes
✔ Add Cypress for both Unit and End-to-End testing? … No / Yes
✔ Add ESLint for code quality? … No / Yes
Scaffolding project in /Applications/MAMP/htdocs/vue/my-router-project...
Done. Now run: // 準備(Scaffolding)ができたので、続いて以下のコマンドを実行
cd my-router-project
npm install
npm run dev
必要なパッケージをインストール
続いてコマンドのレスポンス Done. Now run: の下に記載されている以下のコマンドを実行してプロジェクトの雛形を完成させます。
cdコマンドで作成されたプロジェクトのディレクトリに移動npm installコマンドで必要な JavaScriprt パッケージ(依存関係)をインストールnpm run devコマンドで開発サーバを起動
% cd my-router-project return //プロジェクトディレクトリに移動 % npm install return //必要なパッケージをインストール added 35 packages, and audited 36 packages in 4s 5 packages are looking for funding run `npm fund` for details found 0 vulnerabilities % npm run dev return //開発サーバを起動 > my-router-project@0.0.0 dev > vite VITE v3.1.8 ready in 260 ms ➜ Local: http://127.0.0.1:5173/ //この URL にアクセス ➜ Network: use --host to expose
これで、Vue Router が組み込まれたプロジェクトの雛形の作成が完了です。
npm run dev コマンドのレスポンスに出力された http://127.0.0.1:5173/ にアクセスするとプロジェクト直下にある index.html ファイルが読み込まれて以下のような初期画面が表示されます。
About ページのリンクをクリックすると以下のように表示されます。
開発サーバーを終了するには control + c を押します。
プロジェクトのファイル構成
以下は先述の npm create vite@latest と npm install の実行によりデフォルトで生成されるプロジェクトのフォルダーとファイルの例です。
my-router-project //プロジェクトのディレクトリ ├── index.html //表示用フィル ├── node_modules ├── package-lock.json ├── package.json ├── public │ └── favicon.ico ├── src │ ├── App.vue // メインコンポーネント(Root Component) レンダリングの起点 │ ├── assets │ │ ├── base.css │ │ ├── logo.svg │ │ └── main.css │ ├── components // ページで利用するコンポーネントのディレクトリ │ │ ├── HelloWorld.vue │ │ ├── TheWelcome.vue │ │ ├── WelcomeItem.vue │ │ └── icons │ ├── main.js //エントリポイント(ルーターの有効化) │ ├── router │ │ └── index.js //ルーティングの定義とルーターの生成 │ └── views //ページを構成するコンポーネント(Route Components)のディレクトリ │ ├── AboutView.vue // About ページのコンポーネント │ └── HomeView.vue // Home ページのコンポーネント └── vite.config.js //Vite の設定ファイル
以降では、この構成のままでルーターの機能などを確認しますが、実際の使用では App.vue を編集して不要な部分を削除し、合わせて不要なファイル(aseets のファイルや components のファイルなど)やディレクトリを削除したり、必要なファイルやディレクトリを追加するなどして使用します。
package.json
この例の場合、package.json は以下のようになっています。
- vue のバージョン:3.2.38
- vue-router のバージョン:4.1.5
{
"name": "my-router-project",
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview --port 4173"
},
"dependencies": {
"vue": "^3.2.38",
"vue-router": "^4.1.5"
},
"devDependencies": {
"@vitejs/plugin-vue": "^3.0.3",
"vite": "^3.0.9"
}
}
実際にインストールされているパッケージのバージョンは package-lock.json や npm view xxxx コマンドで確認できます。
vite.config.js
また、vite.config.js は以下のように初期状態では、@ を使ってコンポーネントなど ./src 以下のファイルをインポートできるように ./src を @ で表すエイリアス(resolve.alias)が設定されています。
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
//パスにエイリアスを設定
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
fileURLToPath(url) | new URL(url, import.meta.url) | import.meta
index.html
以下は表示用ファイル index.html です。
アプリをマウントする id 属性が app の div 要素と、type 属性に module を指定して main.js を読み込んでいる script 要素が記述されています。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
AboutView.vue / HomeView.vue
view ディレクトリに配置されている AboutView.vue と HomeView.vue はページを構成するコンポーネント(Route Components)のサンプルで、これらのコンポーネントはルーティングで指定してページを表示する際に使用されるコンポーネントでビュー(View)とも呼ばれます。
以下は About のリンクをクリックした際に表示される About ページのサンプル AboutView.vue です。
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>
<style>
@media (min-width: 1024px) {
.about {
min-height: 100vh;
display: flex;
align-items: center;
}
}
</style>
以下は Home のリンクをクリックした際に表示される(http://127.0.0.1:5173/ で表示される)ホームのサンプル HomeView.vue です。
このサンプルのビューは、components ディレクトリ(ページで利用するコンポーネントのディレクトリ)にある TheWelcome コンポーネントを呼び出しています。
そして TheWelcome コンポーネントでは、同じ components ディレクトリにある WelcomeItem コンポーネントなどを呼び出して作成されています。
<script setup>
import TheWelcome from '../components/TheWelcome.vue'
</script>
<template>
<main>
<TheWelcome />
</main>
</template>
以下では生成されたプロジェクトの主なファイルから Vue Router の動作の概要を確認しています。
router/index.js
src/router/index.js では createRouter メソッドでオプションにルーティングの定義などを指定してルーター(Router インスタンス)を生成し、変数 router に格納し、デフォルトエクスポートしています。
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
// createRouter メソッドでルーターを生成
const router = createRouter({
// History の実装方法を指定(history オプション)
history: createWebHistory(import.meta.env.BASE_URL),
// ルーティングの定義(routes オプション)
routes: [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue')
}
]
})
//生成したルーターをデフォルトエクスポート
export default router
createRouter
Vue Router を利用するには、createRouter でルーター(Router インスタンス)を生成します。
createRouter には以下のようなオプションを指定することができます。
| オプション | 説明 |
|---|---|
| history | History の実装方法(モード)を指定します。Hash Mode を使用する場合は createWebHashHistory メソッドで、HTML5 Mode を使用する場合は createWebHistory メソッドで指定します(history オプション)。 Hash Mode の場合
const router = createRouter({
history: createWebHashHistory(),
routes: [
//...
],
})
|
| routes | ルーティング情報をオブジェクトの配列で定義します。 |
| linkActiveClass | 現在のページを表すアクティブなリンクに適用されるクラス名。デフォルトは router-link-active |
| linkExactActiveClass | 完全一致する現在のページを表すアクティブなリンクに適用されるクラス名。デフォルトは router-link-exact-active |
| scrollBehavior | ページ間を移動する際のスクロールの動作を指定する関数。スクロールを遅らせる Promise を返すことができます。 |
| parseQuery | クエリを解析するためのカスタム実装。 |
| stringifyQuery | クエリオブジェクトを文字列に変換するためのカスタム実装。先頭に ? を付けるべきではありません。 |
history オプション
createRouter メソッドでルーターのインスタンスを生成する際に指定する history オプションでは、以下のような History の実装方法(モード)を指定することができます(Different History modes)。
| モード | 説明 |
|---|---|
| Hash Mode | URL にハッシュ(#)を使う Hash Mode は createWebHashHistory メソッドを使用して指定します。ハッシュベースの Hash Mode はサーバーでの設定を必要としませんが、検索エンジンではページとして処理されないなど SEO 的に不利になります。 |
| HTML5 Mode | ハッシュを利用しない一般的な URL の形式の HTML5 Mode は createWebHistory メソッドを使用して指定します。SEO 的にも問題のない HTML5 Mode の利用が推奨されていますが、サーバーを適切に設定する必要があります(Vite の開発サーバーでは特に設定は必要ありません)。サーバーの設定例は Example Server Configurations で確認できます。 |
| Memory mode | 主に SSR(Server Side Rendering)のためのモードです。履歴(History)がないため、戻ったり、進むことができません。 |
以下は history オプションに HTML5 Mode を指定する例です。引数には基底パス(アプリが配信されているベース URL)を指定しておきます。
const router = createRouter({
// HTML5 Mode を指定
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
//...
],
})
routes オプション(ルーティングの設定・定義)
routes オプション(設定)にはルーティングの情報を route オブジェクトの配列で定義します。
1つの routeオブジェクトが1つのルート(ルーティング)を表します。また、routes オプションの中の各 route オブジェクトはルートレコード(route record)と呼ばれます。
route オブジェクトでは以下のようなプロパティが利用できます。
| プロパティ | 説明 |
|---|---|
| path | リクエスパス(必須) |
| name | ルートの名前(指定しておけば名前付きルートが利用できます) |
| component | このルートによって呼び出されるコンポーネント |
| components | このルートによって呼び出されるコンポーネント(複数)名前付きビュー |
| children | 配下のルーティング(ネストされたルート)の定義 |
| props | ルートパラメータ(route params)を props に割り当てるかどうか |
| redirect | リダイレクト先のパスを指定 |
| alias | エイリアスを指定 |
| meta | ルートのメタ情報(ルートに関する任意の情報) |
以下は src/router/index.js を書き換えて、routes オプション(ルーティングの定義)を createRouter() の外で定義した例です(内容的には全く同じです)。
HomeView.vue や AboutView.vue などのページを構成するコンポーネント(Route Component)は、デフォルトでは src/views ディレクトリに配置します。
それぞれの route オブジェクトでは path や component プロパティなどを使ってルーティングの情報を定義します。以下の1つ目の route オブジェクトでは path で指定した '/' にアクセスしたら、component で指定した HomeView コンポーネントを呼び出すように定義されていて、このルートの名前を name プロパティで home として定義しています。
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue' //ページを構成するコンポーネントをインポート
// ルーティングの情報を route オブジェクトの配列で定義
const routes = [
//route オブジェクト
{
path: '/', //リクエスパス
name: 'home', //このルートの名前
component: HomeView //呼び出されるコンポーネント
},
//route オブジェクト
{
path: '/about',
name: 'about',
// Lazy Loading(動的にインポート)
component: () => import('../views/AboutView.vue')
}
]
// ルーターを生成
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes //routes: routes の省略形(routes オプションに上記で定義した routes を指定)
})
export default router
コンポーネントの動的インポート
上記の例の場合、HomeView コンポーネントは2行目であらかじめインポートしていますが、AboutView コンポーネントはあらかじめインポートせず、ページにアクセスがある場合に動的にインポート(dynamic imports)するようになっています(17行目の記述)。
コンポーネントを動的にインポートするには、以下のように component(複数の場合は components)オプションにコンポーネントを取得するための関数を渡します。
// component プロパティに、コンポーネントを取得するための関数を渡す
component: () => import('../views/AboutView.vue')
必要であれば、別途関数を定義して渡すこともできます。
// コンポーネントを取得するための関数を定義
const importAboutView = () => import('../views/AboutView.vue');
const router = createRouter({
// ...,
routes: [
// ...,
{
path: '/about',
name: 'about',
// 別途定義した関数を渡す
component: importAboutView
}
]
})
ブラウザのデベロッパーツールのネットワークタブで確認すると、初回表示の際に HomeView.vue はダウンロードされますが、AboutView.vue はダウンロードされず、About のリンクをクリックするとダウンロードされるのが確認できます。
また、ビルドすると、以下のように AboutView.0214907f.js と AboutView.4d995ba2.css という AboutView 関連のファイルが別途生成されるのが確認されます。
my-router-project % npm run build return //ビルドを実行 > my-router-project@0.0.0 build > vite build vite v3.1.8 building for production... ✓ 43 modules transformed. dist/assets/logo.da9b9095.svg 0.30 KiB dist/index.html 0.42 KiB dist/assets/AboutView.0214907f.js 0.22 KiB / gzip: 0.19 KiB dist/assets/AboutView.4d995ba2.css 0.08 KiB / gzip: 0.10 KiB dist/assets/index.be4ca81f.css 4.08 KiB / gzip: 1.28 KiB dist/assets/index.ef1713f6.js 81.54 KiB / gzip: 32.03 KiB
index.js を以下のように書き換えて、動的インポートを利用しないと、初回表示の際に HomeView.vue と AboutView.vue の両方のファイルがダウンロードされます。
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
// AboutView もインポート
import AboutView from '../views/AboutView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
// HomeView と同じように以下のように書き換える
component: AboutView
}
]
})
export default router
上記のように書き換えた後にビルドすると、AboutView 関連のファイルは生成されず、index.25027dd1.js と index.cdbbd68c.css にバンドルされてファイルサイズが大きくなります(コンポーネントの数が増えればその差は大きくなります)。
my-router-project % npm run build return //ビルドを実行 > my-router-project@0.0.0 build > vite build vite v3.1.8 building for production... ✓ 42 modules transformed. dist/assets/logo.da9b9095.svg 0.30 KiB dist/index.html 0.42 KiB dist/assets/index.cdbbd68c.css 4.16 KiB / gzip: 1.30 KiB dist/assets/index.25027dd1.js 84.31 KiB / gzip: 33.00 KiB
複数のフィルに分けてビルドすることを Code Splitting と呼びます。
複数のファイルに分けることで、アクセスがあった場合にのみ必要なファイルをダウンロードして、最初のアクセス時にダウンロードするサイズを減らすことができます。
コンポーネントを動的インポートすると Code Splitting が適用され、最初のアクセス時にダウンロードするサイズを減らすことができるので、必要に応じて動的インポートを使用することが推奨されています。
main.js
main.js では router/index.js で生成(定義)した Router オブジェクトの router を Vue のインスタンスにインストール(登録)してアプリで利用できるようにしています。
router/index.js からインポートした router を use() メソッドに渡してプラグインとしてインストールすることで、ルーターが利用できるようになります。
3行目の router のインポートではディレクトリの ./router だけを指定してファイルの index.js を指定していませんが、ディレクトリを指定した場合、ディレクトリ配下の index.js が読み込まれます。
import { createApp } from 'vue'
import App from './App.vue'
import router from './router' //ルーターを router/index.js からインポート
import './assets/main.css' //CSS のインポート
const app = createApp(App) //Vue のインスタンスを生成
app.use(router) //Vue のインスタンスにルーターをインストールして有効化
app.mount('#app')
app.use(router) でルーターを有効化すると、任意のコンポーネント内で this.$router でルーター(router オブジェクト)に、this.$route で現在のルート(route オブジェクト)にアクセスできます。
テンプレート内では this なしで、$router や $route にアクセスすることができます。
但し、Comosition API の setup 内では this にアクセスできないので、useRouter(router オブジェクトを返すメソッド)や useRoute(route オブジェクトを返すメソッド)を使用します。
App.vue
createApp() の引数に渡される App コンポーネントは、アプリケーションにアクセスされた場合にレンダリングの起点として使われるメインコンポーネント(Root Component)で、script setup 構文を使って SFC で記述されています。
※ Root と Route が日本語では同じ「ルート」なので、ここでは Root Component をルートコンポーネントではなく、メインコンポーネントと呼んでいます。
App.vue の <template> では、RouterLink コンポーネントのタグ <RouterLink> を使ってページへのリンクを作成し、RouterView コンポーネントのタグ <RouterView> を使ってルートにマッチしたコンポーネントを表示する領域を作成しています。
<script setup>
// RouterLink コンポーネントと RouterView コンポーネントをインポート
import { RouterLink, RouterView } from 'vue-router'
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<header>
<img alt="Vue logo" class="logo" src="@/assets/logo.svg" />
<div class="wrapper">
<HelloWorld msg="You did it!" />
<nav>
<!-- リンクには RouterLink コンポーネントを使い、リンク先を to プロパティに指定 -->
<!-- デフォルトで <RouterLink> は <a> タグとして描画される -->
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/about">About</RouterLink>
</nav>
</div>
</header>
<!-- ルートとマッチしたコンポーネントが RouterView コンポーネントへ描画される -->
<RouterView />
</template>
<style scoped>
・・・中略・・・
/* 完全一致する現在のページを表すアクティブなリンク */
nav a.router-link-exact-active {
color: var(--color-text);
}
nav a.router-link-exact-active:hover {
background-color: transparent;
}
・・・中略・・・
</style>
RouterLink コンポーネントや RouterView コンポーネントのタグ名(コンポーネント名)はパスカルケースで記述してあります。
以下のようにケバブケースで記述することもできますが、単一ファイルコンポーネントのテンプレート内では、コンポーネント名は常にパスカルケースにすることが推奨されています。但し、DOM テンプレートの中ではケバブケースです。
<template>
<header>
<img alt="Vue logo" class="logo" src="@/assets/logo.svg" />
<div class="wrapper">
<hello-world msg="You did it!" />
<nav>
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
</nav>
</div>
</header>
<router-view />
</template>
RouterLink コンポーネント
RouterLink はユーザーのナビゲーションを有効にするためのコンポーネントで、ルーター経由でページを遷移するには、<a> タグの代わりに <RouterLink> または <router-link> タグを利用します。
RouterLink タグでは a タグの href 属性の代わりに to 属性(プロパティ)を使ってリンク先(パスを表す文字列や移動先情報のオブジェクト)を指定します。
<RouterLink to="/about">About</RouterLink>
上記は以下のようにレンダリングされます。
<a href="/about">About</a>
to プロパティはリンクする対象のルート(route)を表します。クリックされた時に to プロパティの値が内部的に router.push() に渡されます。
to 属性には、v-bind: を使って式や移動先情報のオブジェクト(routeLocation)を渡すこともできます。以下は全て同じリンク先を表しています。
<!-- v-bind: を使った javascript 式-->
<RouterLink v-bind:to="'/about'">About</RouterLink>
<RouterLink v-bind:to="'/ab' + 'out'">About</RouterLink>
<!-- 移動先情報のオブジェクト(path プロパティ)で指定 -->
<RouterLink v-bind:to="{ path: '/about' }">About</RouterLink>
<!-- 移動先情報のオブジェクト(name プロパティ)で指定(名前付きルート) -->
<RouterLink v-bind:to="{ name: 'about' }">About</RouterLink>
<template> で <RouterLink> タグを使わずに以下のように HTML の <a> タグを使うとページの移動は行われますが、リンクをクリックするたびにページ全体が再読み込みされてしまいます。
<nav> <a href="/">Home</a> <a href="/about">About</a> </nav>
移動先情報のオブジェクトで指定できるプロパティ
v-bind: を使って移動先情報のオブジェクトを指定する場合、path や name など以下のようなプロパティを指定することができます。
| プロパティ | 説明 |
|---|---|
| path | ルートのパス |
| name | ルートの名前 |
| params | ルートパラメータ(route params) |
| query | クエリ情報(クエリ文字列の key:value ペアのオブジェクト) |
| hash | ハッシュ情報(# を含めた文字列) |
| meta | メタ情報(ルートに関する任意の情報) |
例えば、以下のリンクをクリックすると、それぞれ /xxxx?q=abc&s=123、/about#foo に遷移します。
<RouterLink v-bind:to="{ path: '/xxxx', query: {q:'abc', s: '123'} }">
XXXX
</RouterLink>
<!-- /xxxx?q=abc&s=123 -->
<RouterLink v-bind:to="{ path: '/about', hash: '#foo'}">
About foo
</RouterLink>
<!-- /about#foo -->
パラメータで query を指定する場合は、key と value のペアのオブジェクト { key:value, ... } で指定します。URL のクエリ文字列は、? の後に「key=value」の形式で、値が複数ある場合は & でつなげて追加されます。
router-link-active クラス
RouterLink では対象のルートがマッチした時に、出力される a 要素に router-link-active や router-link-exact-active クラス(現在のページを表すアクティブなリンクを表すクラス)を自動的に付与します。
これらのクラスを使ってアクティブなリンクにスタイルを指定することができます。
以下のようなリンクがあり、現在のページが Home の場合、
<nav> <RouterLink to="/">Home</RouterLink> <RouterLink to="/about">About</RouterLink> </nav>
出力は以下のようになり、Home のリンクに .router-link-active と .router-link-exact-active が付与されます。また、aria-current="page" も付与されています。
<nav> <a href="/" class="router-link-active router-link-exact-active" aria-current="page">Home</a> <a href="/about" class="">About</a> </nav>
例えば、<RouterLink to="/hello"> というリンクを記述して、現在のページの URL が /hello から始まる場合は、router-link-active クラスが付与されます。
現在のページの URL が /hello/world の場合も、 /hello から始まるるので、router-link-active クラスが付与されます。
<RouterLink to="/hello"> と <RouterLink to="/hello/world"> という2つのリンクがある場合、現在のページの URL が /hello/world の場合、両方のリンクには router-link-active クラスが付与され、 <RouterLink to="/hello/world"> のみに、router-link-exact-active クラスが付与されます。
デフォルトのクラス名を変更
これらのクラス名(router-link-active と router-link-exact-active)は、createRouter() の linkActiveClass 及び linkExactActiveClass オプションで変更することができます。
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue')
}
],
//デフォルトのクラス名を変更
linkActiveClass: 'active',
linkExactActiveClass: 'exact-active',
})
RouterView
RouterView コンポーネントは現在のパスに対してマッチした(定義したルーティングに応じた)コンポーネントを描画するコンポーネントで、 <RouterView> または <router-view> タグを使います。
<template>
<header>
<img alt="Vue logo" class="logo" src="@/assets/logo.svg" />
<div class="wrapper">
<HelloWorld msg="You did it!" />
<nav>
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/about">About</RouterLink>
</nav>
</div>
</header>
<!-- ルートにマッチしたコンポーネントが RouterView コンポーネントへ描画される -->
<RouterView />
</template>
この例の場合、8行目の <RouterLink to="/about">About</RouterLink> のリンクをクリックすると、ルーティングの定義により <RouterView /> の部分に以下の About ページのコンポーネント(AboutView.vue)が描画されます。
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>
<style>
/* 省略 */
</style>
出力は以下のように <RouterView /> の部分に AboutView コンポーネントが出力されます。
<div id="app" data-v-app="">
<header data-v-7a7a37b1="">
<img alt="Vue logo" class="logo" src="/src/assets/logo.svg">
<div class="wrapper">
<div class="greetings"><!-- HelloWorld コンポーネントの出力 -->
・・・中略・・・
</div>
<nav>
<a href="/" class="">Home</a>
<a href="/about" class="router-link-active router-link-exact-active" aria-current="page">About</a>
</nav>
</div>
</header>
<!-- 以下が AboutView コンポーネントの出力 -->
<div class="about">
<h1>This is an about page</h1>
</div>
</div>
Vue Devtools で確認
ブラウザの拡張機能の Vue Devtools を入れていれば、以下のように現在の RouterView やルートなどの情報を簡単に確認できます。
router / route オブジェクト
app.use(router) でルーターを有効化すると、任意のコンポーネント内で this.$router として router オブジェクトのインスタンスにアクセスできます。また、this.$route として現在のルート(route オブジェクト)にアクセスできます。
テンプレート内では単に(this を介さず)、$router や $route でアクセスすることができます。
以下は AboutView.vue に記述を追加して router と route オブジェクトをコンソールやテンプレートに出力して確認する例です。
<script>
export default {
created() {
//コンポーネント内では this を使ってアクセス
console.log(this.$router);
console.log(this.$route);
}
}
</script>
<template>
<div class="about">
<h1>This is an about page </h1>
<!-- テンプレート内では this なしで、$router や $route にアクセス-->
<p>{{ $router.options.routes[0].name}}</p> <!-- home と出力-->
<p>{{ $route.path}}</p> <!-- /about と出力 -->
</div>
</template>
以下は About ページでのコンソールへの出力例です。それぞれのオブジェクトのプロパティやメソッドが確認できます。
Vue Devtools を使えば、route オブジェクトのプロパティなどを簡単に確認することができます。
Comosition API の場合
但し、Comosition API の setup 内では this にアクセスできないので、useRouter() メソッドや useRoute() メソッドを使用します。
- useRouter :ルーターインスタンス(router オブジェクト)を返します。 テンプレート内で
$routerを使用するのと同じです。 - useRoute :現在のルート(route オブジェクト)を返します。テンプレート内で
$routeを使用するのと同じです。
<script setup>
//useRouter と useRoute メソッドをインポート
import { useRouter, useRoute } from 'vue-router';
// router インスタンスを取得
const router = useRouter();
//route インスタンスを取得
const route = useRoute();
console.log(router);
console.log(route);
</script>
<script>
//useRouter と useRoute メソッドをインポート
import { useRouter, useRoute } from 'vue-router';
export default {
setup() {
// router インスタンスを取得
const router = useRouter();
// route インスタンスを取得
const route = useRoute();
console.log(router);
console.log(route);
return {
//テンプレートで使う場合は返す(但し、テンプレートでは $router と $route でアクセス可能)
router,
route
}
}
}
</script>
$route のプロパティ
$route はマッチした現在のルートの route オブジェクト(route location)、つまり、現在のルートの情報を表すオブジェクトのインスタンスです。
$route には以下のようなプロパティがあります。
パラメータを使った動的ルーティング
パス(URL)の末尾にパラメータの文字列(params)を指定して、その値をルーター経由でコンポーネントに引き渡すことができます。
route オブジェクトの path プロパティにパラメータを指定
ルーティング情報を表す route オブジェクトの path プロパティに、コロン : を使ってパラメータの文字列(params の値)を指定します。
path プロパティに指定した :文字列 の部分を動的セグメントと呼びます。
以下は router/index.js の routes オプションに UserView コンポーネントへのルートを追加した例です。
この例の場合、path オプションに指定した /user/:uid の :uid の部分が動的に変わる部分(動的セグメント)で、これにより /user/001 や /user/12 などの URL は同じルートにマッチします。
ルートがマッチした時、この動的セグメントの値はそのルートがマッチしたコンポーネント内で this.$route.params.xxxx として利用可能になります。
$route は route オブジェクトを参照します。また、xxxx の部分はこの場合、uid になります。
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue')
},
//以下のルート(route オブジェクト)を追加
{
path: '/user/:uid', // :uid が動的セグメント
name: 'user',
component: () => import('../views/UserView.vue')
}
],
})
export default router
route オブジェクトの path に指定したパラメータを受け取るコンポーネント
上記の routes オプションに追加したルートに対応するコンポーネント UserView.vue を追加します。
ルートがマッチした時、このコンポーネントでは動的セグメントの値を this.$route.params.uid として取得できます。テンプレートの中では単に $route.params.uid で参照できます(this は不要)。
<template>
<div class="user">
<h1>User : {{ $route.params.uid}}</h1>
</div>
</template>
リンクにパラメータを渡す
リンクにパラメータの値を指定してコンポーネントに渡します。
App コンポーネントに UserView へのリンクを追加し、パラメータの値(001)を指定します。
<script setup>
import { RouterLink, RouterView } from 'vue-router'
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<header>
<img alt="Vue logo" class="logo" src="@/assets/logo.svg" />
<div class="wrapper">
<HelloWorld msg="You did it!" />
<nav>
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/about">About</RouterLink>
<!-- リンクを追加し、パラメータの値を指定 -->
<RouterLink to="/user/001">User 001</RouterLink>
</nav>
</div>
</header>
<RouterView />
</template>
追加したリンク「User 001」をクリックすると、以下のように表示され、パラメータの値が UserView コンポーネントに渡されていることが確認できます。
複数のパラメータ(動的セグメント)を指定
1 つのルートが複数の動的セグメントを持つこともでき、複数のパラメータを指定することができます。
前述の例では、1つの動的セグメント :uid を指定しましたが、/user/:uid/:name のように複数指定することもできます。
{
path: '/user/:uid/:name', //:uid と :name を配置
name: 'user',
component: () => import('../views/UserView.vue')
}
<template>
<div class="user">
<h1>User : {{ $route.params.uid }}</h1>
<h2>Name : {{ $route.params.name }}</h2>
</div>
</template>
<nav> <RouterLink to="/">Home</RouterLink> <RouterLink to="/about">About</RouterLink> <RouterLink to="/user/001/foo">User 001</RouterLink> </nav>
パラメータの変更を監視
Vue Router の場合、例えば、/user/001 から /user/002 へ移動するようなパラメータだけが異なるページ遷移では同じコンポーネントインスタンスが再利用されます。
このため、コンポーネントのアンマウント処理が行われず、ライフサイクルフックが呼ばれません。
以下はパラメータ uid を受け取って created() ライフサイクルフックでその値を更新して出力するように前述の UserView.vue を書き換えた例です。
<script>
export default {
data() {
return {
//uid を受け取ってデータを作成
userId: this.$route.params.uid
}
},
created() {
//created フックで値を更新(但し、これは呼ばれないので更新されない)
this.userId = this.$route.params.uid;
},
}
</script>
<template>
<div class="user">
<h1>User : {{ $route.params.uid }}</h1>
<p>User ID : {{ userId }}</p>
</div>
</template>
以下は上記のスクリプト部分を Composition API の script setup で書き換えたものです。
Composition API では、setup に定義した処理が beforeCreate と created に相当するので created はありません。
<script setup>
//ref メソッドをインポート
import { ref } from 'vue';
//useRoute メソッドをインポート
import { useRoute } from 'vue-router';
//route インスタンス($route)を取得
const route = useRoute();
//ref メソッドでリアクティブな変数 userId を定義
const userId = ref(route.params.uid);
</script>
<template>
<div class="user">
<h1>User : {{ $route.params.uid }}</h1>
<p>User ID : {{ userId }}</p>
</div>
</template>
ルートの定義は以下のように動的セグメントとして :uid を指定しています。
{
path: '/user/:uid', //:uid を配置
name: 'user',
component: () => import('../views/UserView.vue')
}
メニューには、User 009 へのリンクを追加しています。
<nav> <RouterLink to="/">Home</RouterLink> <RouterLink to="/about">About</RouterLink> <RouterLink to="/user/001/foo">User 001</RouterLink> <RouterLink to="/user/009/foo">User 009</RouterLink> </nav>
上記の設定で、/user/001 から /user/009 へリンクをクリックしてページを移動すると、直接 $route.params.uid で出力している部分は更新されますが、created() が呼ばれないため userId の部分は更新されません。
$route オブジェクトを watch
同じコンポーネントでパラメーター変更を検知するためには(上記の問題を回避するには)、 $route オブジェクトを監視(watch)します。
以下は Options API の例です。$route オブジェクトを watch オプションを使って監視して、変更があれば userId を更新します。
<script>
export default {
data() {
return {
userId: this.$route.params.uid
}
},
created() {
this.userId = this.$route.params.uid;
},
// watch オプションを追加
watch: {
$route(to) {
this.userId = to.params.uid;
}
},
}
</script>
以下は Composition API の watch メソッドで書き換えたものです。
<script setup>
// watch をインポート
import { watch, ref } from 'vue';
import { useRoute } from 'vue-router';
const route = useRoute();
const userId = ref(route.params.uid);
// watch メソッド
watch(
route, // $route オブジェクトを監視
(to) => {
userId.value = to.params.uid;
}
)
</script>
上記では $route オブジェクト全体を監視していますが、この例の場合、以下のように $route オブジェクトのプロパティや値を監視するように書き換えても同じ結果になります。
//$route オブジェクトの params プロパティを監視する場合
watch(
() => route.params, // params プロパティを監視
(toParams) => {
userId.value = toParams.uid;
}
)
//または uid のみ監視する場合
watch(
() => route.params.uid, // uid を監視
(toUid) => {
userId.value = toUid;
}
)
以下は、{JSON} Placeholder という JSON データを返してくれる API にアクセスして、ダミーのユーザーデータを取得してページに表示する例です。
ユーザーデータを取得する URL は「https://jsonplaceholder.typicode.com/users/id」になるので、「id」の部分をパラメータの値を整数に変換して指定しています。
関連ページ:Fetch API fetch() の使い方
この例の場合も、パラメータの変更を watch しないと、ページの内容が更新されません。
前述の UserView.vue を以下のように書き換えます。ルートの定義(index.js)やリンク(App.vue)は前述の例と同じです。
<script setup>
//watch, ref メソッドをインポート
import { watch, ref } from 'vue';
//useRoute メソッドをインポート
import { useRoute } from 'vue-router';
//route インスタンスを取得
const route = useRoute();
//ref メソッドでユーザー情報を入れるリアクティブな変数(ref)を初期化
const user = ref({});
//パラメータ uid の文字列を整数に変換
let userId = parseInt(route.params.uid);
//API から fetch() でユーザー情報を取得して変数に代入する関数
const fetchUser = async (url) => {
//API からユーザー情報を取得
const response = await fetch(url);
//取得したレスポンスを json に変換
const data = await response.json();
//user(ref)の value プロパティに、取得したデータを代入
user.value = data;
};
//API の URL(ユーザーの ID 部分を除く)
const targetUrl = 'https://jsonplaceholder.typicode.com/users/';
//引数に渡す URL を userId を使って作成して上記関数を実行
fetchUser(targetUrl + userId);
// params を監視して更新(以下の記述がないとページ遷移の際に更新されない)
watch(
() => route.params,
(toParams) => {
// 変更があれば uid を整数に変換して fetchUser() を実行
userId = parseInt(toParams.uid);
fetchUser(targetUrl + userId);
}
)
</script>
<template>
<div class="user">
<h1>User : {{ route.params.uid}}</h1>
<ul>
<li>Name : {{ user.name }}</li>
<li>Email : {{ user.email }}</li>
<li>Phone : {{ user.phone }}</li>
</ul>
</div>
</template>
例えば、以下のように表示されます。watch メソッドで route.params を監視しているので、/user/001 から /user/009 へリンクをクリックしてページを移動すれば、ユーザーの情報も更新されます。
beforeRouteUpdate ナビゲーションガード
watch を使用する代わりに beforeRouteUpdate ナビゲーションガードを利用することもできます。
Composition API の場合は、onBeforeRouteUpdate を使用します。
<script setup>
import { ref } from 'vue';
//onBeforeRouteUpdate メソッドをインポート
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
const route = useRoute();
const user = ref({});
let userId = parseInt(route.params.uid);
const fetchUser = async (url) => {
const response = await fetch(url);
const data = await response.json();
user.value = data;
};
const targetUrl = 'https://jsonplaceholder.typicode.com/users/';
fetchUser(targetUrl + userId);
// watch の代わりに beforeRouteUpdate ナビゲーションガードを利用
onBeforeRouteUpdate(async (to) => {
userId = parseInt(to.params.uid);
fetchUser(targetUrl + userId);
});
</script>
<script>
import { ref } from 'vue';
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
export default {
setup() {
const route = useRoute();
const user = ref({});
let userId = parseInt(route.params.uid);
const fetchUser = async (url) => {
const response = await fetch(url);
const data = await response.json();
user.value = data;
};
const targetUrl = 'https://jsonplaceholder.typicode.com/users/';
fetchUser(targetUrl + userId);
onBeforeRouteUpdate(async (to) => {
userId = parseInt(to.params.uid);
await fetchUser(targetUrl + userId);
});
return {
route,
user
}
}
}
</script>
関連項目:ナビゲーションガード
404 Not Found Route
ルーティングに設定していない(存在しない)ページの URL へアクセスがあった場合、コンソールに警告は出力されますが、エラーが発生するわけではなく App コンポーネントに設定した内容のみ表示されます。
存在しないページの URL へアクセスがあった場合に、Not Found(404)のページが表示されるようにするには以下のようなルートを設定します。
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView
},
・・・中略・・・
// Not Found(404)へのルート
{
path: '/:pathMatch(.*)*', //すべてに一致するパス
name: 'NotFound',
component: () => import('../views/NotFoundView.vue')
},
],
})
export default router
通常のパラメータは、/ で区切られた URL フラグメント間の文字のみに一致します。
何かに一致させたい場合は、パラメータの直後(末尾)に括弧 ( ) を追加してその中に正規表現を指定することで、パラメータに独自の正規表現を使用できます。
path: '/:pathMatch(.*)*' の (.*) はすべてに一致し、リクエストされた URL のパスの / 以降の値は $route.params.pathMatch で取得できます。:pathMatch(.*)* の最後の * は繰り返しを意味するパラメータで、:pathMatch(.*)* は :pathMatch(.*) の繰り返しです。
上記の Not Found(404)へのルートの定義で component に指定した以下のようなコンポーネントを作成すれば、存在しない URL にアクセスされた際に表示されます。
<template>
<div class="not-found">
<h1>404 Not Found</h1>
<p>指定したページは存在しません。</p>
<p><RouterLink v-bind:to="{ name: 'home' }">ホーム</RouterLink></p>
<!-- <p>:pathMatch(.*)* の値:{{ $route.params.pathMatch }}</p>(確認用)-->
</div>
</template>
history オプションに HTML5 Mode モード を使用する場合は、正しいサーバの設定も必要になります(Vite の開発サーバーでは特に設定は必要ありません)。
例えば、存在しないパス「/xxx/yyy/zzz」にアクセスされると NotFoundView コンポーネントが表示され、pathMatch(.*)* の値は [ "xxx", "yyy", "zzz" ] になります(6行目のコメントを外すと確認できます)。
ルートのマッチング構文
Vue Router では、ルートのパラメータに * や ? などの特定の文字やカスタム正規表現パターンを使ったマッチング構文により、複雑なパスを表現できるようになっています。
正規表現パターンを使ったマッチング
パラメータの末尾に正規表現パターンを括弧 ( ) で括って指定すると、その正規表現パターンに合致した値だけがマッチします。
内部的には、例えば :userId というパラメータの指定は :userId([^/]+) と同じことで、([^/]+) の括弧の中は「スラッシュ以外の少なくとも1文字」というような正規表現になります。
例えば /:orderId と /:productName というルートは、全く同じ URL がマッチします。
この2つを分けてルーティングさせるには、パスに固定のセクションを追加する方法があります。
const routes = [
// /o/3549 や /o/foo などにマッチ
{ path: '/o/:orderId', ... },
// /p/books や /p/123 などにマッチ
{ path: '/p/:productName', ... },
]
但し、場合によっては /o や /p などの固定のセクションを追加したくないこともあります。
例えば、orderId は常に数値で、productName は何でもよいというような場合は、以下のようにパラメーターに続く括弧内にカスタム正規表現を指定できます。
以下の場合、/25 のような数値は /:orderId にマッチし、それ以外は /:productName にマッチします。ルート配列の順序は重要ではありません。
const routes = [
// /:orderId -> /25 や /123 などの数値のみにマッチ( \\d+ は1つ以上の数値 )
{ path: '/:orderId(\\d+)', ... },
// /:productName -> 何にでもマッチ
{ path: '/:productName', ... },
]
(\\d+) の \d は数値を意味し、+ は1つ以上を意味します。また、\ は文字列リテラルでは利用できないので、\\のようにバックスラッシュでエスケープします。
例えば、以下のようにルートを定義すると、(\\d{1,3}) は1〜3桁の数値を表すので、/user/009 や /user/12、 /user/3 にはマッチしますが、/user/foo や /user/1234 にはマッチしません。
{
path: '/user/:uid(\\d{1,3})',
name: 'user',
component: () => import('../views/UserView.vue')
},
関連ページ:JavaScript の正規表現/ RegExp
繰り返しパラメータ(*)
/first/second/third のような複数セクションのルートにマッチさせるには、* や + を使います。
*:0回以上の値にマッチ+:1回以上の値にマッチ
const routes = [
// /:chapters -> /one, /one/two, /one/two/three, などにマッチ(/ にはマッチしない)
{ path: '/:chapters+', ... },
// /:chapters -> /, /one, /one/two, /one/two/three,などにマッチ
{ path: '/:chapters*', ... },
]
例えば、/:chapters* の場合、/one/two/three にマッチし、$route.params.chapters の値は [ "one", "two", "three" ] になります。
* や + を指定した場合、/one/two/three のような複数のセクションからなるパスのパラメータの値は / で分割された結果が配列として返されます。
また、* や + は括弧で指定した正規表現の後に指定することもできます。
以下は数値のセクションの繰り返しのみにマッチします。
const routes = [
// /1, /1/2, /1/2/3, などにマッチ
{ path: '/:chapters(\\d+)+', ... },
// /, /1, /1/2, /1/2/3 などにマッチ
{ path: '/:chapters(\\d+)*', ... },
]
オプショナルパラメータ(?)
? を使うことでパラメーターをオプション(任意)とすることもできます。? は0回または1回の繰り返しを意味し、{0,1} と同じ意味です。
const routes = [
// /users や /users/posva にマッチ
{ path: '/users/:userId?' },
// /users や /users/42 にマッチ
{ path: '/users/:userId(\\d+)?' },
]
sensitive/strict オプション
デフォルトでは、すべてのルートは大文字と小文字を区別せず、また、末尾のスラッシュの有無に関わらずルートにマッチします。 例えば /users は、/users、/users/、/Users/ にマッチします。
このデフォルトの動作は、sensitive と strict オプションを使用して設定できます。これらは、router レベル(全てのルートに適用)と route レベル(個々のルートに適用)の両方で設定できます。
- sensitive : 大文字と小文字を区別するかどうか
- strict : 末尾のスラッシュの有無を区別するかどうか
以下の場合、 4行目のルートは、/users/posva にマッチしますが、router レベルの strict: true により /users/posva/ にマッチせず、route レベルの sensitive: true により /Users/posva にもマッチしません。
5行目のルートは、router レベルの strict: true により /users、/Users、および /users/42 にはマッチしますが、/users/ または /users/42/ にはマッチしません。
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/users/:id', sensitive: true, ... },
{ path: '/users/:id?', ... },
],
strict: true, // 全てのルートに適用(router レベル)
})
ルートの優先順位
ルートはより明示的に指定されたものが優先して適用されます。
例えば、/users/:id と /:product/:name の場合、固定値が入っている /users/:id の方がより明示的にパスを表しているので、優先順位が高くなります。
※ ルートの優先順位はルートの記述の順序には関係ありません。
ルートの定義が複雑になってきた場合は、Path Ranker ツールなどを利用して、ルートの優先順位を確認することができます。
また、Vue Devtools の Routes タブでもスコアを確認することができます。
名前付きルート(Named Routes)
createRouter メソッドでルーター(Router インスタンス)を生成する際に routes オプションの name プロパティでそのルートに名前を設定していれば、名前を使ってルートを特定することができます。
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
//routes オプション
routes: [
・・・中略・・・
{
path: '/about',
name: 'about', //name プロパティに名前を設定
component: () => import('../views/AboutView.vue')
},
{
path: '/user/:uid',
name: 'user', //name プロパティに名前を設定
component: () => import('../views/UserView.vue'),
},
],
})
名前を付けたルートにリンクするには、RouterLink コンポーネントの to プロパティ(属性)にオブジェクトを渡して、name プロパティと必要なプロパティを指定します。
以下の2行目の RouterLink の場合、上記ルーティングの path にパラメータ(:uid)を指定しているので、オブジェクトの name プロパティと params プロパティを指定します。
<RouterLink :to="{ name: 'about' }">About</RouterLink>
<RouterLink :to="{ name: 'user', params: { uid: '003' }}">User 003</RouterLink>
これは push() メソッドを呼び出すときに引数に渡すオブジェクトと同じです。
<button type="button" v-on:click="$router.push({ name: 'about' })">About</button>
<button
type="button"
v-on:click="$router.push({ name: 'user', params: { uid: '003' } })">
User 003
</button>
上記の RouterLink、push() メソッドのどちらの場合もルーターはそれぞれ /about と /user/003 のパスにナビゲーションします。
名前付きルートを利用すると、コンポーネントのパスが変更になった場合でも、コンポーネントで設定したそれぞれの RouterLink や push() の設定は影響を受けないという利点があります(ルーティングの定義 routes/index.js の path の設定の変更のみで済みます)。
ネストされたルート(Nested Routes)
以下のようなネストしたコンポーネントを作成して、これに対してルートを設定することができます。
<RouterView> の中で描画されるコンポーネントも、コンポーネントを描画するための <RouterView> を持つ(入れ子にする)ことができます。
以下は App.vue のテンプレートです。ここの <RouterView /> はトップレベルの RouterView で、トップレベルのルートに対してマッチしたコンポーネントが描画されます。
そして、マッチして描画されたコンポーネントも、同様に(ネストされた)<RouterView /> を持つことができます。
<template>
<header>
<img alt="Vue logo" class="logo" src="@/assets/logo.svg" />
<div class="wrapper">
<HelloWorld msg="You did it!" />
<nav>
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/about">About</RouterLink>
<RouterLink to="/user/001">User 001</RouterLink>
</nav>
</div>
</header>
<RouterView /><!-- トップレベルの RouterView -->
</template>
以下は UserView コンポーネントのテンプレートに <RouterView /> を1つ追加した例です。
<template>
<div class="user">
<h1>User : {{ $route.params.uid}}</h1>
<RouterView /><!-- ネストされた RouterView -->
</div>
</template>
このネストされた RouterView にコンポーネントを描画するには、ルーティングの設定(routes オプション)でネストされたルート(nested route)を設定する必要があります。
children オプションでネストされたルート(nested route)を設定
ネストされたルート(ここでは子ルートと呼ぶことにします)は children オプションを使用します。
children オプションは routes オプション同様、ルート設定オブジェクト(route オブジェクト)の配列で、子ルートを必要なだけ指定することができます。
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
・・・中略・・・
{
path: '/user/:uid',
name: 'user',
component: () => import('../views/UserView.vue'),
//children オプション(route オブジェクトの配列で指定)
children: [
{
// /user/:uid/profile にマッチすると
// UserProfile が UserView の RouterView に描画される
path: 'profile',
component: () => import('../views/UserProfile.vue'),
},
{
// /user/:uid/posts にマッチすると
// UserPosts が UserView の RouterView に描画される
path: 'posts',
component: () => import('../views/UserPosts.vue'),
},
],
},
],
})
export default router
子ルートの path は、先頭に / を付けずに親ルートからの相対パスとして指定します。上記の path: 'profile' は /user/:uid/profile にマッチします。
先頭に / を付けると、親ルートからの相対パスではなく、絶対パスとみなされてしまいます。
子コンポーネント(ネストされるコンポーネント)を作成
以下は子ルートに指定した描画する子コンポーネント(UserProfile と UserPosts)の例です。
<template>
<div class="user-profile">
<h2>User Profile</h2>
</div>
</template>
<style>
.user-profile {
margin: 20px 0;
padding: 20px;
border: 1px solid rgb(125, 161, 138);
background-color: rgb(194, 248, 229);
}
</style>
<template>
<div class="user-posts">
<h2>User Posts</h2>
</div>
</template>
<style>
.user-posts {
margin: 20px 0;
padding: 20px;
border: 1px solid rgb(211, 136, 213);
background-color: rgb(252, 233, 247);
}
</style>
/user/001/profile にアクセスすると以下が表示されます。
/user/001/posts にアクセスすると以下が表示されます。
既定で描画するコンポーネント(空のネストされたパス)
この状態で、/user/001 にアクセスすると、いずれの子ルートもマッチしないので、以下のようにネストされたコンポーネントは描画されません。
空のネストされたパスを指定することで、既定の子ルートとして指定したコンポーネントを描画することができます。
以下では、既定で描画するネストされたコンポーネント UserHome のルートを追加しています。
また、以下の設定(状態)で親のルートの name プロパティを指定していると警告が表示されるため、コメントアウトして無効にしています(関連:ネストされた名前付きルート)。
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
・・・中略・・・
{
path: '/user/:uid',
//name: 'user', // この場合 name を指定していると警告が表示される
component: () => import('../views/UserView.vue'),
children: [
//既定の子ルートを追加
{
//空のネストされたパスを指定
path: '',
component: () => import('../views/UserHome.vue'),
},
{
path: 'profile',
component: () => import('../views/UserProfile.vue'),
},
{
path: 'posts',
component: () => import('../views/UserPosts.vue'),
},
],
},
],
})
<template>
<div class="user-home">
<h2>User Home</h2>
</div>
</template>
/user/001 にアクセスすると、以下のように UserHome が既定でネストされて描画されます。
ネストされた名前付きルート
以下のような親ルートが(name プロパティが指定されている)名前付きルートで、空のパスを指定したネストされたルートがあると、「user という名前のルートには、名前のない子と空のパスがあります。 その名前を使用しても空のパスの子はレンダリングされないため、代わりに名前を子に移動することをお勧めします。これが意図的なものである場合は、子ルートに名前を追加して警告を削除します。」のような警告がコンソールに出力されます。
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
・・・中略・・・
{
path: '/user/:uid',
name: 'user', // 警告が表示される
component: () => import('../views/UserView.vue'),
children: [
{
//空のネストされたパスを指定
path: '',
component: () => import('../views/UserHome.vue'),
},
{
path: 'profile',
component: () => import('../views/UserProfile.vue'),
},
{
path: 'posts',
component: () => import('../views/UserPosts.vue'),
},
],
},
],
})
この状態で、例えば以下のような name プロパティを使ったリンクをクリックしても UserHome は描画されません。
<RouterLink :to="{ name: 'user', params: { uid: '009' }}">User 009</RouterLink>
解決方法としては警告に表示されているように、親の name プロパティを子に移動します。これにより、上記の name プロパティを使ったリンクが機能します。
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
・・・中略・・・
{
path: '/user/:uid',
component: () => import('../views/UserView.vue'),
children: [
{
//空のネストされたパス
path: '',
name: 'user', // 親の name プロパティを子に移動
component: () => import('../views/UserHome.vue'),
},
{
path: 'profile',
component: () => import('../views/UserProfile.vue'),
},
{
path: 'posts',
component: () => import('../views/UserPosts.vue'),
},
],
},
],
})
または、以下のように親と子の両方のルートに name プロパティを指定すれば警告は表示されません。
但し、:to="{ name: 'user', params: { uid: '009' }}" でリンクすると、/user/009 にナビゲートされ、子コンポーネントの UserHome は描画されませんが、そのページで再読込すると、子コンポーネントの UserHome が描画されてしまいます。
{
path: '/user/:uid',
name: 'user', // name プロパティを指定
component: () => import('../views/UserView.vue'),
children: [
{
//空のネストされたパス
path: '',
name: 'user-home', // name プロパティを指定
component: () => import('../views/UserHome.vue'),
},
{
path: 'profile',
component: () => import('../views/UserProfile.vue'),
},
{
path: 'posts',
component: () => import('../views/UserPosts.vue'),
},
],
},
名前付きビュー(Named Views)
ネストをさせずに同時に複数の RouterView を配置することで、複数のビュー(ルートにマッチするコンポーネントの描画)を表示することができます。
但し、それぞれのビューを区別するために、RouterView に name 属性を使って任意の名前を付ける必要があります(名前を指定しない RouterView はその name 属性の値として default が付与されます)。
また、ルートの定義(routes オプション)では複数のコンポーネントを割り当てられるように、呼び出されるコンポーネントの指定を component ではなく、components (複数形) オプションを使用します。
components オプションでは「名前:コンポーネント」の形式で指定します。
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
//components オプションに描画する複数のコンポーネントを指定してインポート
components: {
// この例では動的インポートを使用
default: () => import('../views/AboutView.vue'), //名前のない RouterView
sub: () => import('../views/FooView.vue'),
footer: () => import('../views/BarView.vue')
}
},
{
path: '/user/:uid',
name: 'user',
//複数のコンポーネントを指定してインポート
components: {
default: () => import('../views/UserView.vue'), //名前のない RouterView
sub: () => import('../views/BazView.vue')
}
},
],
})
export default router
<script setup>
import { RouterLink, RouterView } from 'vue-router'
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<header>
<img alt="Vue logo" class="logo" src="@/assets/logo.svg" />
<div class="wrapper">
<HelloWorld msg="You did it!" />
<nav>
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/about">About</RouterLink>
<RouterLink to="/user/001">User 001</RouterLink>
<RouterLink to="/user/009">User 009</RouterLink>
</nav>
</div>
</header>
<!-- RouterView に name 属性を使って名前を指定 -->
<RouterView /> <!-- name 属性を指定しない RouterView の名前は default になる -->
<RouterView name="sub" />
<RouterView name="footer" />
</template>
<template>
<div class="about">
<h1>This is an about page </h1>
</div>
</template>
以下は name="sub" の RouterView に描画されるコンポーネントの例です。
<template>
<div class="foo">
<h2>This is Foo section. </h2>
</div>
</template>
<style>
.foo {
border: 1px solid rgb(139, 182, 148);
margin: 50px 0 10px;
padding: 30px;
background-color: rgb(192, 248, 204);
grid-column-start: 1;
grid-column-end: 3;
}
</style>
<template> <div class="bar"> <h2>This is Bar section. </h2> </div> </template>
<template> <div class="baz"> <h2>This is Baz section. </h2> </div> </template>
例えば、/about にアクセスすると以下が表示されます。
ネストされた名前付きビュー
ネストされたビューを持つ名前付きビューを使用して複雑なレイアウトを作成することができます。
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue')
},
{
path: '/user/:uid',
name: 'user',
component: () => import('../views/UserView.vue'),
//ネストされたルート
children: [
{
path: 'profile',
name: 'user-profile',
//ネストされた名前付きビュー
components: {
default: () => import('../views/FooView.vue'),
content: () => import('../views/UserProfile.vue'),
}
},
{
path: 'posts',
name: 'user-posts',
//ネストされた名前付きビュー
components: {
default: () => import('../views/BarView.vue'),
content: () => import('../views/UserPosts.vue'),
}
},
],
},
],
})
export default router
<script setup>
import { RouterLink, RouterView } from 'vue-router'
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<header>
<img alt="Vue logo" class="logo" src="@/assets/logo.svg" />
<div class="wrapper">
<HelloWorld msg="You did it!" />
<nav>
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/about">About</RouterLink>
<RouterLink to="/user/001">User 001</RouterLink>
<RouterLink to="/user/009">User 009</RouterLink>
</nav>
</div>
</header>
<RouterView /><!-- トップレベルの RouterView -->
</template>
<template>
<div class="user">
<h1>User : {{ $route.params.uid}}</h1>
<RouterLink :to="`/user/${$route.params.uid}/profile`">Profile</RouterLink> |
<RouterLink :to="`/user/${$route.params.uid}/posts`">Posts</RouterLink>
<!-- ネストされた名前付きビュー -->
<RouterView /><!-- default -->
<RouterView name="content" />
</div>
</template>
この例の場合、それぞれのルートに名前(name オプション)を付けているので、上記のリンクは以下のように記述することもできます。
<RouterLink :to="{ name: 'user-profile', params: { uid: $route.params.uid }}">
Profile
</RouterLink> |
<RouterLink :to="{ name: 'user-posts', params: { uid: $route.params.uid }}">
Posts
</RouterLink>
<template>
<div class="user-profile">
<h2>User Profile</h2>
</div>
</template>
<template>
<div class="user-posts">
<h2>User Posts</h2>
</div>
</template>
<template>
<div class="foo">
<h2>This is Foo section. </h2>
</div>
</template>;
<template>
<div class="bar">
<h2>This is Bar section. </h2>
</div>
</template>
パラメータを props として渡す
コンポーネントで $route を使用する(パラメータを $route オブジェクト経由で受け渡しする)と、特定の URL(ルート経由)でしかそのコンポーネントを使用できないため、ルート(route)との密結合が作成され、コンポーネントの柔軟性が制限されます。
これは必ずしも悪いことではありませんが、props オプションを使って、コンポーネントをルーターから切り離すことができます。
パラメータをコンポーネントの props に引き渡すには、ルーティングの定義で props オプションを追加し、コンポーネント側では対応する props を定義します。
これにより、コンポーネントをどこからでも使用できるようになり、コンポーネントの再利用とテストが容易になります。
Boolean モード
props を true に設定すると、route.params がコンポーネントのプロパティとして設定されます。
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
・・・中略・・・
{
path: '/user/:uid',
name: 'user',
component: () => import('../views/UserView.vue'),
props: true //props オプションを追加
},
],
})
export default router
UserView コンポーネント側では、props を定義します。これにより、テンプレートでは $route.params.uid ではなく、uid でアクセスできるようになります。
<script>
export default {
// props を定義
props: {
uid: String
}
}
</script>
<template>
<div class="user">
<h1>User : {{ uid }}</h1><!-- props の uid でアクセス -->
</div>
</template>
script setup 構文では defineProps で定義します。
<script setup>
// script setup では defineProps で定義
const props = defineProps({
uid: String
});
</script>
Object モード
props がオブジェクトの場合、コンポーネントプロパティとしてそのまま設定されます。固定値で props を指定する場合に便利です。
{
path: '/user/:uid',
name: 'user',
component: () => import('../views/UserView.vue'),
props: { uid: 100 } //オブジェクトとして固定値を渡す
}
この例の場合、ルートの定義で props: { uid: 100} のように数値(Number)として指定しているので、props も数値として定義します。この場合、URL に /user/001 でアクセスしても、uid は固定で 100 になります。
<script setup>
const props = defineProps({
uid: Number // 数値に
});
</script>
<template>
<div class="user">
<h1>User : {{ uid }}</h1><!-- uid は数値の 100 で固定 -->
</div>
</template>
Function モード
props を返す関数を作成することができます。これにより、パラメータの型を変換するなどが可能です。
$route.params の戻り値は文字列ですが、例えば、UserView コンポーネントが uid プロパティ(props)を数値(Number)として受け取る場合、以下のように props を返す関数を使うことで型を変換することができます。
props を返す関数は、引数として $route オブジェクトを受け取り、戻り値として props をオブジェクトで返します。また、オブジェクトを表す { } が関数ブロックとして認識されてしまうため、戻り値全体を括弧 ( ) で囲んでいます(オブジェクトリテラルの返却)。
{
path: '/user/:uid',
name: 'user',
component: () => import('../views/UserView.vue'),
//props を返す関数で数値(Number)に変換
props: routes => ({
uid: Number(routes.params.uid)
})
},
<script setup>
const props = defineProps({
uid: Number
});
</script>
<template>
<div class="user">
<h1>User : {{ uid }}</h1><!-- uid は数値(例: 009 は 9 に) -->
</div>
</template>
名前付きビュー
名前付きビューを使用する場合、名前付きビューごとに props オプションを定義する必要があります。
以下は Vue Router ドキュメント(Passing Props to Route Components/Named views)から
const routes = [
{
path: '/user/:id',
// 複数のコンポーネント(名前付きビュー)を指定(components は複数形)
components: {
default: User, // default は名前のない RouterView
sidebar: Sidebar
},
//名前付きビューごとに props オプションを定義(定義しなくても問題ない???)
props: { default: true, sidebar: false }
}
]
リダイレクト
あるパスがリクエストされたときに、redirect プロパティを使って異なるパスにリダイレクトすることができます。
以下は /home にアクセスがあった場合に / にリダイレクトする例です。
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView
},
{
// /home にアクセスがあった場合に / にリダイレクト
path: '/home',
redirect: '/',
},
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue')
},
{
path: '/user/:uid',
name: 'user',
component: () => import('../views/UserView.vue'),
},
],
})
redirect プロパティを指定する場合、レンダリングするコンポーネントがないため、component プロパティを省略できます(但し、そのルートに children と redirect プロパティがある場合は、component プロパティも必要です)。
オブジェクトで指定
リダイレクト先をオブジェクトで指定することもできます。以下は、前述の例を name プロパティを指定したオブジェクトで書き換えた例です。
routes: [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/home',
//オブジェクトで指定(上記ルートの name:'home' のパス '/' へリダイレクト)
redirect: {name: 'home'},
},
・・・
],
関数を使った動的なリダイレクト
関数は引数としてターゲットルート(リクエストされたルートのオブジェクト)を受け取り、リダイレクト先のパスを返します。
以下は /search/screens にアクセスされたら /search?q=screens にリダイレクトする例です。
to.params.searchText は /search/screens の場合、screens になります。query はクエリ文字列(? 以降の文字列)の key=value を指定します。
const routes = [
{
path: '/search/:searchText',
redirect: to => {
// 引数の to はターゲットルートのオブジェクト
// リダイレクト先のパスを返す
return { path: '/search', query: { q: to.params.searchText } }
},
},
{
path: '/search',
// ...
},
]
エイリアス
リダイレクトと似た仕組みにエイリアス(alias)があります。エイリアスは特定のパスに別のパスでもアクセスできるようにする仕組みです。alias を利用することで1つのルーティングに対して複数の URL を設定することができます。
以下は /about に対して /aboutus からでもアクセスできるようにし、/user/:uid に対して /u/:uid からでもアクセスできるようにエイリアスを設定する例です。
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
//エイリアスを設定
alias: '/aboutus',
component: () => import('../views/AboutView.vue')
},
{
path: '/user/:uid',
name: 'user',
//エイリアスを設定
alias: '/u/:uid',
component: () => import('../views/UserView.vue'),
},
],
})
export default router
※ SEO 的には、エイリアスを使う場合は重複コンテンツ対策が求められます。
meta フィールド
ルートを定義する際に meta プロパティ(Route Meta Fields)を含めることができます。
meta は、ルートに関する任意の情報を表すことができる route オブジェクトのプロパティで { key:value } の形式で指定し、任意の名前(key)と値(value)を指定することができます。
以下は /about のルートに meta プロパティ { myMeta: 'foo' } を設定して、その値をナビゲーションガードを使って出力する例です。
ナビゲーションガードの引数に渡される route オブジェクト(to や from) のプロパティ(.meta)として、それらをコンソールに出力しています。
値は名前(key 名)を使って、この場合 to.meta.myMeta や from.meta.myMeta で取得できます。
この場合、/about にアクセスすると、beforeEach と beforeEnter によりそれぞれ以下が出力され、
- beforeEach to.meta.myMeta:
foo - beforeEach from.meta.myMeta:
undefined - beforeEnter to.meta:
{myMeta: 'foo'} - beforeEnter to.meta.myMeta:
foo
/about から他のページに移動するとグローバルガードの beforeEach から以下が出力されます。
- beforeEach to.meta.myMeta:
undefined - beforeEach from.meta.myMeta:
foo
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
・・・中略・・・
{
path: '/about',
name: 'about',
//meta プロパティを設定
meta: { myMeta: 'foo' },
component: () => import('../views/AboutView.vue'),
// beforeEnter ナビゲーションガードでコンソールに出力
beforeEnter: (to) => {
console.log('beforeEnter to:', to.meta); //オブジェクト
console.log('beforeEnter to.meta.myMeta:', to.meta.myMeta); //値
},
},
・・・中略・・・
],
});
// beforeEach グローバルナビゲーションガードでコンソールに出力
router.beforeEach((to, from) => {
console.log('beforeEach to.meta.myMeta:', to.meta.myMeta);
console.log('beforeEach from.meta.myMeta:', from.meta.myMeta);
});
export default router
以下は meta プロパティを設定したルートのコンポーネント(AboutView.vue)で、route オブジェクトのプロパティとして出力する例です。
Composition API では、route オブジェクトにアクセスするには useRoute() を使います。テンプレート内では $route.meta でアクセスすることがでできます。
<script setup>
//useRoute メソッドをインポート
import { useRoute } from 'vue-router';
//route インスタンスを取得(Options API では this.$route でアクセス可能)
const route = useRoute();
//route オブジェクトのプロパティとして出力
console.log(route.meta); // {myMeta: 'foo'} と出力される
console.log(route.meta.myMeta); //foo と出力される
</script>
<template>
<div class="about">
<h1>This is an about page </h1>
<p>{{ $route.meta.myMeta }}</p> <!-- foo -->
<p>{{ route.meta.myMeta }}</p> <!-- foo -->
</div>
</template>
以下は、requiresAuth という名前の「認証が必要かどうか」を表す meta プロパティを設定し、ナビゲーションガードを使って、requiresAuth の値と、すでに認証されているかどうかを表す変数 isLoggedIn の値によって、ページへのアクセスを制御する例です。
この場合、/user/:uid/posts のルートは meta: { requiresAuth: false } なのでリダイレクトされませんが、/user/:uid/profile のルートは、requiresAuth: true で、且つ isLoggedIn が false なので、37行目の return により /user/:uid にリダイレクトされます。
例えば、/user/003/posts にはアクセスできますが、/user/003/profile にアクセスしようとすると、/user/003 にリダイレクトされます。
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/user/:uid',
name: 'user',
component: () => import('../views/UserView.vue'),
children: [
{
path: 'profile',
// meta に requiresAuth: true を設定
meta: { requiresAuth: true },
component: () => import('../views/UserProfile.vue'),
},
{
path: 'posts',
// meta に requiresAuth: false を設定
meta: { requiresAuth: false },
component: () => import('../views/UserPosts.vue'),
},
],
},
],
});
//すでに認証されているかどうかを表す変数
const isLoggedIn = false;
//グローバルナビゲーションガード beforeEach
router.beforeEach((to) => {
// meta の requiresAuth が true で、isLoggedIn が false の場合はリダイレクト
if (to.meta.requiresAuth && !isLoggedIn) {
return { path: `/user/${to.params.uid}` };
}
});
export default router
RouterLink v-slot 属性
RouterLink はデフォルトではアンカータグ <a> を生成しますが、スコープ付きスロットを使って出力をカスタマイズすることもできます。
以下はリンクをボタンで出力する例です。RouterLink 要素に custom 属性を指定して、v-slot 属性には分割代入で利用するプロパティを取り出しています。
スロットに渡すコンテンツの button 要素では分割代入で取り出したプロパティを使って、クリックした場合の動作やクラスを設定しています。
<RouterLink to="/about" custom v-slot="{ route, navigate, isActive }">
<button
v-on:click="navigate"
v-bind:class="{ 'router-link-active': isActive }">{{ route.name }}</button>
</RouterLink>
スロットプロパティに分割代入を使わず、例えば slotProps(任意の名前)で受け取って使う場合は、以下のように記述することもできます。また、v-slot="slotProps" は v-slot:default="slotProps" の省略形です。
<RouterLink to="/about" custom v-slot="slotProps">
<button
v-on:click="slotProps.navigate"
v-bind:class="{ 'router-link-active': slotProps.isActive }">{{ slotProps.route.name }}</button>
</RouterLink>
上記のボタンをクリックすると、v-on:click="navigate" により to で指定された /about に移動します。マウスオーバーで遷移するには v-on:mouseover="navigate" と指定します。
移動先のページ(この例の場合は /about)では例えば、以下のような出力になります。移動先が現在の URL にマッチしているので、isActive が true になり router-link-activ クラスが出力されます。ボタンのラベルは、route.name でルートの名前を出力しています。
<button class="router-link-active">about</button>
custom 属性
RouterLink 要素に custom 属性を指定すると、コンテンツを a タグでラップしません。スロットを利用して出力をカスタマイズする場合などに指定します。
v-slot 属性
スロットプロパティを受け取る v-slot 属性には以下のプロパティを持つオブジェクトが渡されます。
| プロパティ | 説明 |
|---|---|
| href | リンク先のパス(a 要素の href 属性) |
| route | リンク先のルートの情報(route オブジェクト)$route のプロパティ |
| navigate | ナビゲーションをトリガーするための関数 |
| isActive | リンク先が現在の URL にマッチしているか(マッチしていれば true になる) |
| isExactActive | リンク先が現在の URL に完全にマッチしているか(マッチしていれば true になる) |
以下はスロットプロパティの内容をコンソールに出力するボタンを App.vue に追加して確認する例です。
<script setup>
import { RouterLink, RouterView } from 'vue-router'
import HelloWorld from './components/HelloWorld.vue'
//コンソールに出力するメソッドの定義
const log = (val) => {
console.log(val);
}
</script>
<template>
<header>
・・・中略・・・
<RouterLink to="/about" custom v-slot:default="slotProps">
<button v-on:click="log(slotProps)">Check</button>
</RouterLink>
・・・中略・・・
</header>
<RouterView />
</template>
Check ボタンをクリックすると、以下のようにスロットプロパティの内容をコンソールで確認できます。
以下は、マウスオーバーで遷移し、デフォルトの RouterLink と同じように router-link-active と router-link-exact-active クラスを出力する例です(v-on: と v-bind: は省略形)。
<RouterLink
to="/about" custom v-slot="{ href, navigate, isActive, isExactActive }">
<button
@mouseover="navigate"
:class="[isActive && 'router-link-active', isExactActive && 'router-link-exact-active']">{{ href }}</button>
</RouterLink>
アクティブクラスを外部要素へ適用
アクティブクラスを a タグ自身よりも、外側の要素に対して適用するには、v-slot を使ってリンクを作成することでラップできます。
<ul>
<RouterLink to="/about" custom
v-slot="{ href, route, navigate, isActive, isExactActive }">
<li :class="[isActive && 'router-link-active', isExactActive && 'router-link-exact-active']">
<a :href="href" @click="navigate">{{ route.fullPath }}</a>
</li>
</RouterLink>
</ul>
<ul>
<li class="router-link-active router-link-exact-active">
<a href="/about">/about</a>
</li>
</ul>
アニメーションの適用
RouterView も RouterLink と同様、スロット機能(スコープ付きスロット)に対応しています。
<RouterView> をスロットを使って書き換えると、以下のように記述することができます。
<RouterView v-slot:default="slotProps"> <component v-bind:is="slotProps.Component"></component> </RouterView>
スロットプロパティに分割代入を使い、デフォルトスロットしかないので v-slot の省略記法を使うと以下のようにも記述できます(v-bind も省略形を使い、タグも閉じタグを省略する記法にしています)。
スロットに渡すコンテンツは、<component> 要素の is 属性にスロットプロパティから取得した Component を指定して動的に表示します。
<RouterView v-slot="{ Component }">
<component :is="Component" />
</RouterView>
スロットプロパティを受け取る v-slot 属性には以下のプロパティを持つオブジェクトが渡されます。
| プロパティ | 説明 |
|---|---|
| Component | 現在のルートで描画するコンポーネント |
| route | 現在のルートの情報(route オブジェクト)$route のプロパティ |
以下はスロットプロパティの内容をコンソールに出力するボタンを App.vue に追加して確認する例です。
<script setup>
import { RouterLink, RouterView } from 'vue-router'
import HelloWorld from './components/HelloWorld.vue'
//コンソールに出力するメソッドの定義
const log = (val) => {
console.log(val);
}
</script>
<template>
<header>
・・・中略・・・
</header>
<RouterView v-slot="slotProps">
<component :is="slotProps.Component"></component>
<button @click="log(slotProps)">Check</button>
</RouterView>
</template>
Check ボタンをクリックすると、以下のようにスロットプロパティの内容をコンソールで確認できます。
アニメーションを適用
<RouterView> 要素を <transition> 要素で囲むことで、ルーティングの際に描画するコンテンツにアニメーション(transition)を適用することができます。
この例ではアニメーションの際にカクカクしないように、トランジションモード mode="out-in" を指定しています。
<script setup>
import { RouterLink, RouterView } from 'vue-router'
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<header>
・・・中略・・・
</header>
<!-- アニメーションを適用 -->
<RouterView v-slot="{ Component }">
<transition mode="out-in" >
<component :is="Component" />
</transition>
</RouterView>
</template>
<style scoped>
/* アニメーションの CSS(デフォルトのトランジションクラス) */
.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0.1;
}
・・・中略・・・
</style>
transition 要素に name 属性を指定
transition 要素に name 属性を指定して、デフォルトのトランジションクラスのクラス名(.v-enter-active などの v の部分)をカスタマイズできます。
<RouterView v-slot="{ Component }">
<transition mode="out-in" name="fade-in">
<component :is="Component" />
</transition>
</RouterView>
上記の場合、name 属性に fade-in を指定しているので、トランジションクラスのクラス名は fade-in-xxxx になります。
<style scoped>
/* アニメーションの CSS(name 属性が fade-in のトランジションクラス) */
.fade-in-enter-active,
.fade-in-leave-active {
transition: opacity 0.5s ease;
}
.fade-in-enter-from,
.fade-in-leave-to {
opacity: 0.1;
}
・・・中略・・・
</style>
再利用されるコンポーネントへのアニメーションの適用
Vue は、類似したコンポーネントを自動的に再利用するため、パラメータを使った動的ルーティングなどのページ遷移では、アニメーションがトリガーされません。
そのような場合にアニメーションを強制的にトリガーするには、<component> 要素に key 属性を追加します。key 属性の値には、スロットプロパティから取得した route の path を利用することができます。
<RouterView v-slot="{ Component, route }">
<transition mode="out-in" name="fade-in">
<component :is="Component" :key="route.path"/>
</transition>
</RouterView>
上記のように設定すると、例えば、/user/001 から /user/007 へのような遷移でもアニメーションが適用されます。
ルート単位でのアニメーション
これまでの例の場合、全てのルートに対して同じアニメーションを適用しますが、ルートにより異なるアニメーションを適用することもできます。
ルートにより異なるアニメーションを適用するには、各ルートの meta フィールドにアニメーションの種類を表す文字列を設定し、その値を使って transition 要素の name 属性を動的に変更してアニメーションを切り替えます。
以下の例では、meta プロパティに animation というキーにアニメーションの種類を表す文字列を設定しています。この例ではキー名を animation としていますが、任意の名前を付けられます。
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
// meta プロパティにアニメーションの種類を表す文字列を設定
meta: { animation: 'slide-in' },
component: HomeView
},
{
path: '/about',
name: 'about',
// meta プロパティ
meta: { animation: 'fade-in' },
component: () => import('../views/AboutView.vue'),
},
{
path: '/user/:uid',
name: 'user',
// meta プロパティ
meta: { animation: 'rotate' },
component: () => import('../views/UserView.vue'),
},
],
});
export default router
transition 要素の name 属性に、meta プロパティの値(route.meta.animation)を指定すれば、ルートによりアニメーションが動的に切り替わります。
以下では、ルートに meta プロパティが設定されていない場合は、fade-in のアニメーションを適用するように指定しています。
そして CSS で meta プロパティに指定した名前を使ったトランジションクラスを使ってアニメーションを定義します。
<script setup>
import { RouterLink, RouterView } from 'vue-router'
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<header>
・・・中略・・・
</header>
<RouterView v-slot="{ Component, route }">
<transition mode="out-in" v-bind:name="route.meta.animation || 'fade-in'">
<component :is="Component" :key="route.path"/>
</transition>
</RouterView>
</template>
<style scoped>
/* fade-in */
.fade-in-enter-active,
.fade-in-leave-active {
transition: opacity 0.5s ease;
}
.fade-in-enter-from,
.fade-in-leave-to {
opacity: 0.1;
}
/* rotate */
@keyframes rotate {
0% {
transform: rotate(360deg);
}
10% {
transform: rotate(160deg);
}
100% {
transform: rotate(0deg);
}
}
.rotate-enter-active {
animation : rotate 0.5s;
}
/* slide-in */
@keyframes slide-in {
0% {
transform: translateX(100%);
}
10% {
transform: translateX(30%);
}
100% {
transform: translateX(0);
}
}
.slide-in-enter-active {
animation : slide-in 0.5s;
}
.slide-in-leave-active {
animation : slide-in 0.2s reverse;
}
・・・中略・・・
</style>
ルーティング時のスクロールの動作
createRouter メソッドでルーターインスタンスを生成する際に scrollBehavior オプションを使って、ページ移動(遷移)時のスクロールの動作を指定することができます。
const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL),
routes: [
・・・中略・・・
],
//scrollBehavior オプション
scrollBehavior (to, from, savedPosition) {
// 遷移後のスクロール位置を返す
}
})
scrollBehavior は以下の引数を取り、戻り値として遷移後のスクロール位置(を表すオブジェクト)を返します。
| 引数 | 説明 |
|---|---|
| to | 遷移先のルート情報(route オブジェクト) |
| from | 遷移元のルート情報(route オブジェクト) |
| savedPosition | 前回のスクロール位置を表すオブジェクト。popstate ナビゲーション (ブラウザの戻る/進むボタンが使用された) 時のみ利用可能。 |
スクロール位置を表すオブジェクトは以下のようなプロパティを使って指定することができます。
| プロパティ | 説明 |
|---|---|
| top left | top:トップからの垂直軸上のピクセル、left:左端からの水平軸上のピクセル。el と一緒に使用する場合は、その要素からの相対距離。例 { top: 0 } |
| el | CSS セレクタや DOM 要素。例 el: '#main' や el: to.hash |
| savedPosition | 前回のスクロール位置を表すオブジェクト |
| behavior | スムーススクロールには smooth を指定 |
以下は、savedPosition が存在する場合(「戻る」を利用した場合)はその位置に移動し、そうでなければ、ハッシュ # がある場合はハッシュで指定された要素の位置へ、なければトップへ移動する例です。
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
・・・中略・・・
],
scrollBehavior(to, from, savedPosition) {
//前回のスクロール位置があれば
if (savedPosition) {
//その位置を保持
return savedPosition;
} else {
//ハッシュがある場合は
if (to.hash){
return {
//ハッシュで指定された要素の位置へ
el: to.hash,
//スムーススクロール
behavior: 'smooth',
}
//上記以外は先頭へ
} else {
return { top: 0 }
}
}
}
});
export default router
スクロールを遅延させる
例えば、ルーティングの際にアニメーションを適用する場合では、スクロールする前にアニメーションが終了するのを待ちたいかもしれません。
スクロールを遅延させるには、スクロール位置を返す Promise を返します。
以下はスクロールする前に500ミリ秒待機する例です。
scrollBehavior(to, from, savedPosition) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ left: 0, top: 0 })
}, 500)
})
},






























