vue-cliで作成したプロジェクトでVue Routerを設定
本文書ではvue-cliコマンドを利用して作成したvueプロジェクトでのVue Routerの設定手順について説明を行なっています。最新のvue-cliのバージョンを利用するとVueのバージョンを選択することができます。本文書では最新バージョンのVue CLI v4.5.11で動作確認を行っているためVue2, Vue3の両方で動作確認を行っています。
Vue 2, Vue 3の両方の環境を使って説明を行っていますが本文書内でのバージョン間の違いはVueとRouterのインスタンス化に関するコードが記述されているmain.jsとrouter/index.jsファイルの2つとコンポーネントのroot要素が必ず1つという制限がVue 3からなくなったことです。Vue Routerのルーティングの設定については違いはありません。
Vue Routerを利用することでページを移動する際にページの再読み込みを行わずページ内での更新が必要な箇所のみ更新が行われるためスムーズにページ遷移を行うことができます。Vue.jsでシングルページアプリケーションを構築したい場合はVue Routerを利用することになります。
Vue Routerを初めて使用する人であれば本文書を読む前に下記の文書を読んでVue Routerの基本を理解しておくことをおすすめします。
目次
vue.jsのプロジェクトの作成
vue createコマンドを使ってvueプロジェクトの作成を行います。プロジェクトの名前は任意です。ここではvue-routerという名前で作成しています。指定した名前のフォルダがvueコマンドを実行したフォルダ内に作成されます。
$ vue create vue-router
Vue CLI v4.5.11
? Please pick a preset:
Default ([Vue 2] babel, eslint)
Default (Vue 3 Preview) ([Vue 3] babel, eslint)
❯ Manually select features
プロジェクトの作成だけではなくRouterのインストールも同時に行うので、manually select features(手動での機能選択)を選択してください。manually select featuresではインストールする機能を選択できるので、Routerを選択してください。他の機能を使う予定がある場合は他のものも選択して進めてください。
? Please pick a preset: Manually select features
? Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to invert selection)
◉ Choose Vue version
◉ Babel
◯ TypeScript
◯ Progressive Web App (PWA) Support
❯◉ Router
◯ Vuex
◯ CSS Pre-processors
◉ Linter / Formatter
◯ Unit Testing
◯ E2E Testing
Vueのバージョンの選択画面が表示されます。2.xまたは3.xを選択してください。
? Choose a version of Vue.js that you want to start the project with (Use arrow
keys)
❯ 2.x
3.x (Preview)
次にhistory mode(ヒストリーモード)を使用するかどうか聞かれるのでYを選択してください。
? Use history mode for router? (Requires proper server setup for index fallback
in production) (Y/n)
linterを聞かれるので選択を行なってください。本文書では、デフォルトのままでEnterを押します。
? Pick a linter / formatter config: (Use arrow keys)
❯ ESLint with error prevention only
ESLint + Airbnb config
ESLint + Standard config
ESLint + Prettier
本文書ではLint on saveのままEnterを押します。
? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i
> to invert selection)
❯◉ Lint on save
◯ Lint and fix on commit
下記ではpackege.jsonを選択します。各機能の設定ファイルがpackage.jsonの中に含まれるか個別のファイルとして作成するかの選択です。
? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? (Use arrow keys)
In dedicated config files
❯ In package.json
今回の設定を保存するのか聞かれますがNを選択します。
? Save this as a preset for future projects? (y/N)
ここまでの選択が完了するとインストールが開始されます。
インストールが完了したら作成したディレクトリに移動して、npm run serveコマンドを実行してください。
$ cd vue-router/
$ npm run serve
・
・
DONE Compiled successfully in 5661ms
App running at:
- Local: http://localhost:8080/
- Network: http://192.168.2.114:8080/
Note that the development build is not optimized.
To create a production build, run npm run build.
実行後、ブラウザでhttp://localhost:8080/にアクセスすると下記の画面が表示されます。Vue Routerのインストールを行っているのでページ上部にHomeとAboutのリンクが表示されルーティングがデフォルトで設定されています。ルーティングが自動で設定されているためその設定コードを見ながらVue Routerの理解を深めることができます。
構成ファイルの確認
インストールが完了するとnode_modules、public, srcの3つのフォルダが作成されます。
srcフォルダの中でアプリケーションコードを記述していきます。アプリケーションコードの中でもっとも重要なメインファイルであるmain.jsファイルでVue Routerの設定を確認していきます。
main.jsファイルの確認(Vue3)
Routerをインストールするとmain.jsファイルではimport router from ‘./router’が追加されています。インポートされているrouterはsrcフォルダの下にあるrouterフォルダのindex.jsファイルです。index.jsファイルの名前の場合はindex.jsファイルの名前を省略しても自動でrouterフォルダにあるindex.jsファイルをimportしてくれます。createAppでVueのインスタンスを作成し、useメソッドでimportしたrouterを指定後にmountメソッドでidがappを持つdiv要素にマウントしています。<div id=”app”>の内側がvue.jsの制御下に置かれることになります。
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App).use(router).mount('#app')
main.jsファイルの確認(Vue2)
Routerをインストールするとmain.jsファイルではimport router from ‘./router’が追加されています。インポートされているrouterはsrcフォルダの下にあるrouterフォルダのindex.jsファイルです。index.jsファイルの名前の場合はindex.jsファイルの名前を省略しても自動でrouterフォルダにあるindex.jsファイルをimportしてくれます。
Vueインスタンスを作成する際の引数のオブジェクトのプロパティにrouterが追加され登録されています。$mount(‘#app’)によりpublicのindex.htmlにある<div id=”app”>にvue.jsがマウントされます。<div id=”app”>の内側がvue.jsの制御下に置かれることになります。
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')
router.jsファイルの確認(Vue3)
routerフォルダのindex.jsファイルにはルーティングに関する情報が記述されてVue Routerの設定ファイルです。/(ルート)と”/about”の2つのルーティングがroutesオブジェクトに登録されています。pathとnameとcomponentのプロパティが各ルーティングに設定されています。pathに設定したURLにアクセスした場合にcomponentに指定したコンポーネントファイルの中身が表示されます。nameについては後ほど名前付きルートのところで説明を行っています。
createRouterメソッドでrouterのインスタンスを作成してexportを行っています。aboutのコンポーネントはlazy-loadedが設定されており、ブラウザから/aboutへのアクセスがあった場合にAbout.vueコンポーネントが読み込まれます。
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
export default router
router.jsファイルの確認(Vue2)
router/index.jsファイルにはルーティングに関する情報が記述されています。ルーティングには”/”(ルート)と”/about”の2つが登録されています。aboutのコンポーネントはlazy-loadedが設定されており、ブラウザから/aboutへのアクセスがあった場合にAbout.vueコンポーネントが読み込まれます。
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
import About from './views/About.vue'
Vue.use(Router)
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ './views/About.vue')
}
]
})
lazy-loadedの設定について
lazy-loadedを設定しない場合、大きなサイズのvueファイルが存在するとビルドされて作成されるjsファイルも大きくなります。jsファイルのファイルサイズが大きい場合ブラウザからアクセスがあるとjsファイルの読み込みに時間がかかってしまうため、遅延を解消する目的でアクセスのあった場合のみ読み込みを行うlazy-loadedが使われています。
App.vueファイルの確認
main.jsの中でApp.vueファイルが指定されているのでブラウザからアクセスがあると最初にApp.vueファイルに記述されている内容がブラウザ上に描写されます。
App.vueファイルのtemplate部分がルーティングに関して重要な部分なので内容を確認していきます。
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
<router-view/>
</div>
</template>
templateタグの中にはdivタグもありますが、Vue Routerに必須なrouter-linkとrouter-viewという2つのタグを確認することができます。この2つのタグがVue Routerに関連するタグで、それぞれ以下のような役割を持っています。
- router-linkタグはリンクを作成するために使用します。
- router-viewタグは、それぞれのリンク先に紐づいた内容を表示するために使用します。
ブラウザに表示される画面とrouterタグの関係は以下の通りです。上部のHomeとAboutがrouter-linkタグによって作成され、その下の大きな赤線で囲んだ部分がrouter-viewタグによって作成されています。
Home.vue, About.vueファイルについて
router-linkタグでtoに設定されている”/”(ルート)と”/about”は/route/index.jsファイルに記述されているroutesプロパティのpathに対応します。”/”(ルート)にアクセスするとコンポーネントのHomeの内容がrouter-viewタグの場所に表示されます。
routes: [
{
path: '/',
name: 'Home',
component: Home
},
Home.vueの中のtemplateタグを確認するとHelloWorldタグが使われています。HelloWorldタグを利用するためにHelloWorld.vueがimportされていることがわかります。importしたコンポーネントはcomopentsで設定を行う必要があります。
“/”(ルート)にアクセスした時に表示される内容は大半がHelloWorld.vueに記述されている内容です。
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'
export default {
name: 'home',
components: {
HelloWorld
}
}
</script>
About.vueはシンプルで、他のvueファイルを読み込まずh1タグでThis is an about pageを記述しているのみです。
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>
これをブラウザから見ると下記のように表示されます。Home、Aboutのrouter-linkタグの部分は”/”(ルート)と同じ内容で、router-viewタグの部分がAbout.vueのtemplateタグの中で記述した内容が表示されています。
“/about”ページがどのように設定されているかわかれば追加ページを作成することは簡単です。
postページを追加する
/aboutページを参考にpostページを追加してみましょう。
App.vueファイルへのrouter-linkの追加
最初にApp.vueにrouter-linkタグを追加します。router-linkタグのtoには/postを設定しています。
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
<router-link to="/post">Post</router-link>
</div>
<router-view/>
</div>
</template>
npm run serverを実行している場合は、router-linkを追加して保存するとビルドが自動で実行されブラウザに即座に変更内容が表示されます。HomeとAboutと横にPostの文字列が表示され、リンクが貼られている状態になります。
リンクが貼られているのでPostの文字列をクリックすることが可能です。Postのリンクをクリックすると画面は移動しますが上部のリンク以外にはページには何も表示されません。
ページに何も表示されない理由は、router-linkを追加しただけ/postに関するルーティングの追加もルーティングに対応するvueファイルも作成していないためです。/postにアクセスした際にページが表示されるように¥router¥index.jsファイルに新たにpostに関するルーティングの追加を行います。
ルーティングの追加
/router/index.jsファイルを開いてルーティングを追加します。aboutのルーティングを参考に/aboutのルーティングの下にpostのルーティングを追加します。
{
path: '/post',
name: 'post',
component: () => import(/* webpackChunkName: "post" */ './views/Post.vue')
},
上記の内容で保存を行うとPost.vueファイルが存在しないためビルドでエラーが発生します。
vueファイルの作成
About.vueと同様にviewsディレクトリの下にPost.vueファイルを作成します。作成したPost.vueファイルには以下を記述します。
<template>
<div class="post">
<h1>This is a post page</h1>
</div>
</template>
/postに移動するとようやく下記の画面が表示されます。このようにデフォルトで作成されているaboutページを参考に設定を行うことで追加のページも簡単に作成することができます。
動的ルートの設定
ここまで3つのルーティングの設定を行ってきましたが/(ルート)にアクセスするとHome.vue, /aboutにアクセスするとAbout.vue, /postにアクセスするとPost.vueのファイルの内容が表示されURLと表示される内容が1対1に対応していました。
一般的なブログでよく見かける各記事のページのURLが/post/1, /post/2といったようにURLが動的に変わる場合にVue Routerではどのように対応すればいいのか解説していきます。
動的ルートの動作確認を行うためにidとtitleを持つオブジェクトを3つ持った配列postsをデータプロパティにに設定します。
<template>
<div class="post">
<h1>This is an Post page</h1>
</template>
<script>
export default {
data(){
return {
posts: [
{
id:1,
title:'vue.js'
},
{
id:2,
title:'react'
},
{
id:3,
title:'alpine.js'
}
]
}
}
}
</script>
設定したデータプロパティpostsをv-forディレクティブで展開して展開したtitleに対してroute-linkタグでリンクをj貼ります。
<template>
<div class="post">
<h1>This is an Post page</h1>
<div v-for="post in posts" :key="post.id">
<router-link :to="`/post/${post.id}`">
{{ post.title }}
</router-link>
</div>
</div>
</template>
</script>
ブラウザ上ではpostsに設定した3つのタイトルが表示されます。
各タイトルにはリンクを貼っているのでクリックすることはできますが、クリックした先のページでは何も表示されません。/post/1, /post/2, /post/3に対応したルーティングが設定されていないことが原因なのでルーティングの追加を行います。
ルーティングの設定
/router/index.jsファイルにルーティングの設定を追加する必要がありますが動的ルートの場合はアクセスするURLの値が変わるので値が変わる部分は:idとします。idは任意の名前なので変更することは可能で、対応するコンポーネントにはPostShow.vueを設定します。
{
path: '/post/:id',
name: 'PostShow',
component: () => import(/* webpackChunkName: "about" */ '../views/PostShow.vue')
},
PostShow.vueファイルはPost.vueファイルを元に作成を行います。
<template>
<div class="post">
<h1>This is an Post Show page</h1>
</div>
</template>
設定後再度postページに表示されているリンクをクリックするとPostShow.vueファイルに記述した内容が表示されます。Postページに表示されている3つのリンクのどれをクリックしても表示されている内容は同じものとなります。
動的に変わるIDの値の取得方法
内容は同じですが各タイトルのリンク先であるURLは/post/1, /post/2, /post/3のように異なっています。PostShowページの内容は表示されたので次に/postの後ろについている値を取得する方法を確認します。ルーティングに関する情報は$routeオブジェクトに保存されており、コンポーネント上からアクセスすることができます。ライフサイクルフックのmountedでconsole.logを利用してにどのような情報が$routeに入っているか確認します。
<script>
export default {
mounted(){
console.log(this.$route)
}
}
</script>
設定後にブラウザのデベロッパーツールを確認すると$routeオブジェクトの中身が表示されます。fullPathなどでもidの値を確認することができますがparamesのオブジェクトにid:”2″を確認することができます。このことから取得したいidの値は$route.params.idに保存されていることがわかります。
$route.parames.idを利用してブラウザ上にidの値を表示させてみましょう。
<template>
<div class="post">
<h1>This is an Post Show page</h1>
<div>{{ $route.params.id }}</div>
</div>
</template>
設定後には各リンクをクリックすると表示されるページ毎に異なる数字が表示されます。
このように動的に変わるURLのidの値を取得することができました。idを取得することはできましたがどのようにページ毎に表示される内容をidによって動的に変更するのでしょう。通常だとidを使って外部のサーバにアクセスを行いサーバから取得した個別のデータを取得することになります。本文書では外部のサーバを用意していないのでPostに設定したpostsデータをPostShow.vueコンポーネントでも再利用します。
$route.params.idで受け取った値とfindメソッドを利用してidの一致するpostを取得してブラウザ上に表示させます。
<template>
<div class="post">
<h1>This is an Post Show page</h1>
<div>{{ post.id }}</div>
<div>{{ post.title }}</div>
</div>
</template>
<script>
export default {
data(){
return {
posts: [
{
id:1,
title:'vue.js'
},
{
id:2,
title:'react'
},
{
id:3,
title:'alpine.js'
}
]
}
},
computed:{
post(){
return this.posts.find(post => post.id == this.$route.params.id)
}
},
mounted(){
console.log(this.$route)
}
}
</script>
リンク毎に異なる内容を表示することができます。下記ではidを3を持つpostの内容を表示しています。
propsを利用してidを渡す方法
idは$route.paramsから取得することができましたがpropsを利用してidを渡すこともできます。propsを利用する場合は/router/index.jsでpropsプロパティをtrueに設定する必要があります。
{
path: '/post/:id',
name: 'PostShow',
component: () => import(/* webpackChunkName: "about" */ '../views/PostShow.vue'),
props:true,
},
PostShow.vueコンポーネント側でのpropsの設定方法については従来の設定方法と同じです。受け取るpropsの型やrequiredプロパティを設定することができます。computedプロパティのfindメソッドでは$route.params.idからpropsで受け取ったthis.idへの変更も忘れずに行います。
<template>
<div class="post">
<h1>This is an Post Show page</h1>
<div>{{ post.id }}</div>
<div>{{ post.title }}</div>
</div>
</template>
<script>
export default {
props:{
id:{
type:String,
required:true,
}
},
data(){
return {
posts: [
{
id:1,
title:'vue.js'
},
{
id:2,
title:'react'
},
{
id:3,
title:'alpine.js'
}
]
}
},
computed:{
post(){
return this.posts.find(post => post.id == this.id)
}
},
mounted(){
console.log(this.$route)
}
}
</script>
propsを使っても同じようにリンク毎に異なる内容のページを表示させることができます。
404 Not Foundページの設定
ここまでの設定でルーティングに存在しないURL(/pfadfastfsa)にアクセスを行うとコンソールにはメッセージが表示されますがブラウザには何も表示されません。
vue-router.esm-bundler.js?6c02:71 [Vue Router warn]: No match found for location with path "/pfadfasfsa"
routesオブジェクトにpath: ‘/:pathMatch(.)‘,を設定していますが、設定値はVue Routerのバージョン4のドキュメントの値を利用しています。routesオブジェクトの先頭に追加していますが最後に追加しても動作は変わりません。
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import NotFound from '../views/NotFound.vue'
const routes = [
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound },
{
path: '/',
name: 'Home',
component: Home
},
//略
NotFound.vueファイルの作成を行います。
<template>
<div class="post">
<h1>404 Not Found</h1>
</div>
</template>
設定後に先ほどアクセスして何も表示されなかったルーティングに存在しないURL(/pfadfastfsa)にアクセスします。404 Not Foundが画面に表示されれば設定は問題なく行われています。/(ルート), /about, /postにアクセスするとこれまでのように各ページの内容が表示されることも確認してください。
404は存在しないURLにアクセスした場合にユーザにそのページは存在しないというるHTTPのステータスコードの404を意味します。通常のWebサーバを利用している場合はサーバからステータスコード404が戻されます。Vue Routerでは404 Not Fuondの設定前にルーティングに存在しないページにアクセスするとステータスコードは304 Not Modifiedが戻され、404 Not Foundを設定するとステータスコード200が戻されます。Vue Routerではクライアント側(ブラウザ)でルーティング処理が行われているためです。存在しないURLにアクセスした場合にステータスコードの404を戻すためにはサーバ側の設定が必要となります。
次にデータプロパティpostsに存在しない/posts/4にアクセスを行ってみましょう。ページには何も表示されておらずコンソールには”Uncaught (in promise) TypeError: Cannot read property ‘id’ of undefined”が表示されます。idを4を持つpostが存在しないためにエラーが発生しているのでv-ifディレクティブを利用して分岐を行うことで問題を回避することができます。
<template>
<div class="post" v-if="post">
<h1>This is an Post Show page</h1>
<div>{{ post.id }}</div>
<div>{{ post.title }}</div>
</div>
<div class="post" v-else>
<h1>404 Not Found</h1>
</div>
</template>
設定後に/posts/4にアクセスすると404 Not Foundが表示されます。
名前付きルートの設定
routes/index.jsファイル内のルーティングの設定ではルーティング毎にpath, name, componentを設定していました。ここまではpathとcomponentのみ利用していましたがnameは利用していません。nameを利用してリンクを設定することができます。nameを利用した場合はroute-linkでtoにURL(pathの値)を指定するのではなくオブジェクトでnameを設定することができます。実際に変更すると以下のようになります。
<div id="nav">
<router-link :to="{name:'Home'}">Home</router-link> |
<router-link :to="{name:'About'}">About</router-link>
</div>
<router-view/>
nameを利用することでroute/index.jsファイルのpathを変更することがあってもnameが変わらないのでコードの変更を行う必要がなくなります。nameを利用していな場合にroute-linkタグでさまざまなコンポーネント上でリンクを貼っている場合は各タグのtoの値を変更する必要があります。例えばpathをaboutからaboutusに変更した場合にnameを利用している場合はrouter-linkの設定を変更する必要がありませんがnameを利用していない場合はtoの値をaboutからaboutusに変更する必要があります。
モードの変更(Vue2)
これまではhistoryモードで動作確認を行いましたが、別のモードであるhashモードではどのようにルーティング行われるのか確認します。
route.jsファイルに記述されているmode行をコメントします。
export default new Router({
// mode: 'history',
base: process.env.BASE_URL,
ブラウザでhttp://localhost:8080/にアクセスするとURLに#がlocalhost:8080/#/となります。
aboutページに移動しても#が入ったURL(localhost:8080/#/about)となります。
ナビゲーションガードとmetaでtitleの設定
ページのtitleはすべて同じ値(routerのv4.Xではrouter-title)が設定されていますがmetaを利用してページごとに異なるtitleを設定することができます。ルーティングのオブジェクトに新たにmetaを追加してtitleを設定します。
ナビゲーションガードのbeforeEachを利用することで各ルーティングのページが表示される前に引数の関数が実行委されます。documentのtitleにページの移動先であるtoのmetaのtitleを設定しています。
import { createRouter, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue';
const routes = [
{
path: '/',
name: 'Home',
component: Home,
meta: { title: 'Home' },
},
{
path: '/about',
name: 'About',
meta: { title: 'About' },
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () =>
import(/* webpackChunkName: "about" */ '../views/About.vue'),
},
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
});
router.beforeEach((to, from, next) => {
document.title = `${to.meta.title}`;
next();
});
export default router;
設定後にページにアクセスすると各ルーティングに設定したtitleを確認することができます。aboutページにアクセスするとページタイトルにAboutが設定されていることが確認できます。
ナビゲーションガードのbeforeEachはページの移動毎に実行されるので認証を行いたい場合はbeforeEachの中でチェックを行うといったことが可能です。mataにはtitle以外にもパラメータを設定することがでrequiredAuthを設定することでそのルーティングに認証が必要かどうかを分岐させるために利用することもできます。
そのほかにもVue Routerには機能がありますが本文書の内容でVue Routerの理解が読む前よりも深まったのではないでしょうか。