対象者
この記事はVueの基本的な使い方について既に把握している人が対象です
注意
VueRouterの使い方についての記事なのでデザインは簡素なものにしています。
ディレクトリ構成図
router
├── index.html
├── package.json
├── webpack.config.js
└── src
├── main.js
├── routes.js
├── App.vue
└── Components
├── Home.vue
├── Users.vue
├── Items.vue
├── Includes
│ ├── Header.vue
│ └── Footer.vue
└── Info
├── UserInfo.vue
└── ItemInfo.vue
環境構築手順
- routerという名前のフォルダを作って上のディレクトリ構成図と同じ構成のものを作る。(ファイルは下に全て載せているのでコピペして作ってください)
- ターミナルに npm install コマンドを入力してpackage.jsonに書いてある外部ライブラリやパッケージをインストールする。
- ターミナルに npm run dev コマンドを入力すれば サーバーが立ち上がる。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>vue-routerについて</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.5.2/animate.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
</head>
<body>
<div id="app"></div>
<script src="/dist/build.js"></script>
</body>
</html>
{
"name": "vue-router-start",
"description": "VueRouterProject",
"scripts": {
"dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
"build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
},
"dependencies": {
"vue": "^2.6.10",
"vue-resource": "^1.5.1",
"vue-router": "^3.1.2"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
],
"devDependencies": {
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-preset-env": "^1.6.0",
"babel-preset-stage-3": "^6.24.1",
"cross-env": "^5.0.5",
"css-loader": "^0.28.7",
"file-loader": "^1.1.4",
"vue-loader": "^13.0.5",
"vue-template-compiler": "^2.4.4",
"webpack": "^3.6.0",
"webpack-dev-server": "^2.9.1"
}
}
var path = require('path')
var webpack = require('webpack')
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/dist/',
filename: 'build.js'
},
module: {
rules: [
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
],
}, {
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
}
//
}
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
}
]
},
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
},
extensions: ['*', '.js', '.vue', '.json']
},
devServer: {
historyApiFallback: true,
noInfo: true,
overlay: true
},
performance: {
hints: false
},
devtool: '#eval-source-map'
}
if (process.env.NODE_ENV === 'production') {
module.exports.devtool = '#source-map'
// http://vue-loader.vuejs.org/en/workflow/production.html
module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
compress: {
warnings: false
}
}),
new webpack.LoaderOptionsPlugin({
minimize: true
})
])
}
vue-routerのインストール方法
npmを使う場合
npm install vue-router --save
yarnを使う場合
yarn add vue-router
どちらを使うかはお好みで(どっちでもいい)
import Vue from 'vue';
import App from './App.vue';
// ヘッダーとフッターのコンポーネントを読み込む
import Header from './Components/Includes/Header.vue'
import Footer from './Components/Includes/Footer.vue';
// VueRouterを読み込む
import VueRouter from "vue-router";
// ルーティング用のファイル(後述)を読みこむ
import { routes } from "./routes";
Vue.component('vue-header',Header)
Vue.component('vue-footer',Footer)
// Vue.use()を使って上で読み込んだVueRouterのプラグインを使えるようにする
// これをやらないと任意のVueコンポーネント内でVueRouterが使えない
Vue.use(VueRouter);
// ルーターインスタンスを作成して上述のルーティング用のファイルから読み込んだ
// routes(ルートオプション)を渡す
const router = new VueRouter({
routes,
// vue-routerのデフォルトはハッシュモードなのでhistoryモードに変更してハッシュなしでURL遷移できるようにする
// 参考URL https://router.vuejs.org/ja/guide/essentials/history-mode.html
mode: 'history',
// scrollBehaviorについては https://router.vuejs.org/ja/guide/advanced/scroll-behavior.html を参照
scrollBehavior(to,from,savedPosition){
// 遷移先のページのパス
console.log(to);
// 遷移元(すなわち現在)のページのパス
console.log(from);
// 画面遷移したあと遷移元のページに戻った時に遷移元のスクロールポジションがsavedPosition
console.log(savedPosition);
// savedPositionが存在すればsavedPositionの位置から画面が始まる
// savedPositonが存在しなければページの一番上から画面が始まる
// ちなみにsavedPositionはブラウザの戻る・進むボタンを押した時のみ定義される
if(savedPosition){
return savedPosition;
}else{
return{
x:0,
y:0,
}
}
}
})
// グローバルビフォーガード
// 全てのrouter-linkをクリックした際に画面遷移する前に実行される
router.beforeEach((to,from,next)=>{
// 参考URL https://router.vuejs.org/ja/guide/advanced/navigation-guards.html#%E3%82%B0%E3%83%AD%E3%83%BC%E3%83%90%E3%83%AB%E3%83%93%E3%83%95%E3%82%A9%E3%83%BC%E3%82%AC%E3%83%BC%E3%83%89
console.log('global router link');
// next()で次のページへの遷移を許可する
// これはグローバルビフォーガードなのでnext(false)にすると全てのリンクが使用不可になる
next();
})
// Vueインスタンスの中に上で作成したルーターインスタンスが入った定数routerを入れる
new Vue({
el:'#app',
router,
render: h => h(App)
});
VueRouterはデフォルトではhashモードなのでlocalhost:8080/#/userのように#がURLに付加されてしまうので特別な理由がなければhistoryモードに変更する。
ちなみにグローバルビフォーガードの中で
if(to.path === '/items'){
next({path: '/'});
}else{
next();
}
のように記述すれば'/items'にアクセスされた時のみ'/'へリダイレクトするようなことも可能です。
またscrollBehavior()の引数に指定されているto,from,savedPositionについてはconsole.logに出力して自分で確かめてみるとわかりやすい。
import UsersComp from "./Components/Users";
import ItemsComp from "./Components/Items";
import HomeComp from "./Components/Home";
import UserInfoComp from "./components/Info/UserInfo";
import ItemInfoComp from "./Components/Info/ItemInfo";
// main.jsからroutesを読み込むのでexportするのを忘れないこと!!
export const routes = [
{path:'/users',component: UsersComp, children: [
{path:':id',component: UserInfoComp},
],beforeEnter: (to, from, next) => {
// ルート単位ビフォーガード(beforeEnter)というものも設定できる
// これは/userルートへのbeforeEnterなのでnext(false)にすると/userへアクセスできない
next();
}},
{path:'/items', component: ItemsComp, children: [
// ItemsCompにrouter-viewを設定したとするとこの中に入るのは/itemsのchirdrenにあるpathのコンポーネントのみ
{path:':number', component: ItemInfoComp,name: 'itemContent'}
]},
{path: '', component: HomeComp, name:'home'},
]
<template>
<div>
<vue-header/>
<div class="container">
<!-- ルートとマッチしたコンポーネントがここに表示される -->
<router-view/>
</div>
<vue-footer/>
</div>
</template>
<script>
export default {
}
</script>
<style>
body {
padding:0;
margin:0;
}
.container {
min-height: 100vh;
box-sizing: border-box;
padding: 20px;
box-sizing: border-box;
}
</style>
App.vueは基準となるコンポーネントなので/のパスにアクセスされたらHomeCompが、/usersにアクセスされたらUsersCompが、/itemsにアクセスされたらItemsCompがrouter-viewのタグの場所に表示されることになる
(ルーティングについては上のroutes.jsを参照)
<template>
<div>
<header>
<div class="title">VueRouter</div>
<nav>
<!-- router-linkを使うことでリロードなしでURL遷移できる -->
<!-- active-classやexactについては https://router.vuejs.org/ja/api/#tag のリンクを参照 -->
<span><router-link to="/" tag="span" active-class="active" exact>Home</router-link></span>
<span><router-link to="/users" active-class="active" exact>User Index</router-link></span>
<span><router-link to="/items" active-class="active" exact>Item Index</router-link></span>
</nav>
</header>
</div>
</template>
<script>
</script>
<style scoped>
a {
text-decoration: none;
color: inherit;
}
.title {
float: left;
}
nav {
float: right;
}
nav span {
background: #6ac024;
font-size: 20px;
padding: 5px 10px;
border-radius: 3px;
}
nav .active{
color: rgb(228, 185, 46);
}
header {
background: rgb(196, 241, 112);
border-bottom: 4px solid #05d3b7;
box-sizing: border-box;
padding: 20px;
color: #fff;
font-size: 25px;
height: 80px;
}
</style>
VueRouterではaタグの代わりにrouter-linkというタグを使って画面遷移する
router-linkタグはデフォルトではaタグに変換されるがtag="span"のようにすれはspanタグに変更することができるしtag="div"にすればdivタグに変更できる。
これもデベロッパーツールで確かめてみること!
active-class="active"は現在のパスが/usersであればto="/users"となっているrouter-linkのタグのclass属性にactiveというクラスが付加される
またrouter-linkにexactを適用しないとto="/"となっているHome画面へのrouter-linkは現在のパスが/でも/usersでも/itmesでも/から始まるものなら全てactive-classが適用されてしまうので、正確にactive-classを適用したい場合はexactを適用すること
(詳しくは上記のコードにコメントアウトされているURLから公式ドキュメントを参照すること)
<template>
<div>
<footer>
Footer
</footer>
</div>
</template>
<script>
</script>
<style scoped>
footer {
background: rgb(43, 211, 65);
padding: 20px;
color: #fff;
text-align: center;
font-size: 30px;
border-top:4px solid rgb(175, 152, 49);
border-bottom: 4px solid rgb(175, 152, 49);
}
</style>
<template>
<div>
<h1>Home</h1>
<hr>
<p>
sample text sample text sample text sample text sample text sample text
sample text sample text sample text sample text sample text sample text
sample text sample text sample text sample text sample text sample text
sample text sample text sample text sample text sample text sample text
sample text sample text sample text sample text sample text sample text
sample text sample text sample text sample text sample text sample text
sample text sample text sample text sample text sample text sample text
sample text sample text sample text sample text sample text sample text
sample text sample text sample text sample text sample text sample text
sample text sample text sample text sample text sample text sample text
sample text sample text sample text sample text sample text sample text
sample text sample text sample text sample text sample text sample text
sample text sample text sample text sample text sample text sample text
sample text sample text sample text sample text sample text sample text
sample text sample text sample text sample text sample text sample text
sample text sample text sample text sample text sample text sample text
sample text sample text sample text sample text sample text sample text
sample text sample text sample text sample text sample text sample text
sample text sample text sample text sample text sample text sample text
sample text sample text sample text sample text sample text sample text
sample text sample text sample text sample text sample text sample text
sample text sample text sample text sample text sample text sample text
sample text sample text sample text sample text sample text sample text
sample text sample text sample text sample text sample text sample text
</p>
<router-link
tag="button"
:to="{
name:'itemContent',
params:{ number: item_id },
query:{ item:2, show: 'true'},
hash: '#hash',
}">
アイテム3
</router-link>
</div>
</template>
<script>
export default {
data(){
return{
item_id:'',
}
},
mounted(){
this.item_id = 3;
}
}
</script>
<style scoped>
p{
padding: 20px;
min-height: 1000px;
}
button{
padding: 5px 10px;
background: rgb(50, 53, 204);
color: #fff;
border-radius: 5px;
}
</style>
ページ下部のアイテム3のrouter-linkの遷移先のURLは以下のようになる。
http://localhost:8080/items/3?item=2&show=true#hash
上のようなURLになる理由
まずrouter-linkのname属性にitemContentを、params属性に{number: item_id}を指定している。itemContentはsrc/routes.jsを参照すればわかるようにitemInfoCompにname属性として割り当てられていて、パラメータのnumberにはitem_id(ここでは3)が割り当てられているので/items/3のパスが割り当てられているページへ遷移することになる。
またquery属性に{ item:2, show: 'true'}が指定されているので?item=2&show=trueがURLに付加される。
さらにhash属性に'#hash'が指定されているので#hashがURLに付加されている。
<template>
<div>
<h2>ユーザー一覧</h2>
<ul>
<li><router-link to="/users/1" active-class="active" exact tag="button">ユーザー1</router-link></li>
<li><router-link to="/users/2" active-class="active" exact tag="button">ユーザー2</router-link></li>
<li><router-link to="/users/3" active-class="active" exact tag="button">ユーザー3</router-link></li>
</ul>
<router-view/>
</div>
</template>
<style scoped>
ul{
display:flex;
list-style:none;
}
ul button{
padding: 5px 10px;
box-sizing: border-box;
width: 150px;
margin-right: 15px;
background: rgb(231, 182, 76);
color: #fff;
border-radius: 5px;
}
li .active{
color: rgb(110, 56, 209);
}
</style>
<script>
export default {
data(){
return{
permission: false;
}
},
mounted(){
console.log(this.$route.params.id);
},
// コンポーネント内ガード
// /usersにアクセスされる前の挙動
beforeRouteEnter (to, from, next) {
console.log('このページへの遷移許可');
console.log(from);
console.log(to);
// next(false)にすれば/usersにアクセスできない
next();
},
// /usersから離れる時の挙動
beforeRouteLeave (to, from, next) {
if(this.permission){
console.log('次のページに進みます。')
next();
}else{
alert('別のページへの遷移は許可されていません。');
}
},
}
</script>
コンポーネント内ガードといってコンポーネント毎にアクセスを制限することもできる。
beforeRouterEnter()はコンポーネントにアクセスされる前に何らかの処理を行うことができ、
beforeRouterLeave()はコンポーネントから離れる前に何らかの処理を行うことが可能。
<template>
<div>
<h2>アイテム一覧</h2>
<ul>
<li><router-link to="/items/1" active-class="active" exact tag="button">アイテム1</router-link></li>
<li><router-link to="/items/2" active-class="active" exact tag="button">アイテム2</router-link></li>
<li><router-link to="/items/3" active-class="active" exact tag="button">アイテム3</router-link></li>
</ul>
<router-view/>
<div class="panel" @click="backToHome">
<span>>>Homeに戻る</span>
</div>
<div class="panel" @click="goToUsers">
<span>ユーザー一覧</span>
</div>
</div>
</template>
<style scoped>
ul{
display:flex;
list-style:none;
}
ul button{
padding: 5px 10px;
box-sizing: border-box;
width: 150px;
margin-right: 15px;
background: rgb(56, 190, 56);
color: #fff;
border-radius: 5px;
}
</style>
<script>
export default {
methods:{
backToHome(){
this.$router.push({name: 'home'});
},
goToUsers(){
this.$router.push('/users');
}
}
}
</script>
Users.vueではユーザー1のrouter-link(to="users/1")をクリックした場合にrouter-viewのタグの
場所にUserInfoCompのコンポーネントの内容が描画される。
これはUsers.vueに配置されたrouter-viewにはroutes.jsにおいてUsers.vueのchildrenに指定したコンポーネントのみが描画されるからである。
同様にItems.vueにでアイテム1のrouter-link(to="items/1")をクリックした場合にはrouter-viewタグの場所にはItemInfoCompのコンポーネント内容が描画される。
これはroutes.jsにおいてItems.vueのchildrenにItemInfoCompのコンポーネントが指定されてるからである。
this.$router.push()を使うことによってrouter-linkと同様に画面遷移をすることができる。
引数にはパスを指定する方法とコンポーネントのname属性をオブジェクト形式で指定する方法がある。
(コンポーネントのname属性はsrc/routes.jsで定義している。)
<template>
<div>
<div>
ユーザーID:{{ userId }}
</div>
</div>
</template>
<script>
export default {
computed:{
userId(){
return this.$route.params.id;
}
}
}
</script>
<template>
<div>
<div>
アイテムID:{{ itemNumber }}
</div>
</div>
</template>
<script>
export default {
computed:{
itemNumber(){
return this.$route.params.number;
}
}
}
</script>