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) }) },