vue-router入門

#対象者
この記事はVueの基本的な使い方について既に把握している人が対象です

###注意
VueRouterの使い方についての記事なのでデザインは簡素なものにしています。


![ezgif-2-87db9504d52f.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494460/a401c073-13fe-731a-6bd6-e283150f648d.gif)

###ディレクトリ構成図
```bash

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

```

## 環境構築手順
1. routerという名前のフォルダを作って上のディレクトリ構成図と同じ構成のものを作る。(ファイルは下に全て載せているのでコピペして作ってください）
2. ターミナルに npm install コマンドを入力してpackage.jsonに書いてある外部ライブラリやパッケージをインストールする。
3. ターミナルに npm run dev コマンドを入力すれば サーバーが立ち上がる。

```html:index.html

<!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>

```


```json:package.json
{
  "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"
  }
}

```

```js:webpack.config.js

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を使う場合

```bash
  npm install vue-router --save
```

yarnを使う場合

```bash
  yarn add vue-router
```
どちらを使うかはお好みで（どっちでもいい）



  
```js:src/main.js(メインのjsファイル)


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に出力して自分で確かめてみるとわかりやすい。

```js:src/routes.js(ルーティング設定用ファイル)
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'},
]

```
```vuejs:src/App.vue
<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を参照）

```vuejs:src/Components/Includes/Header.vue
<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から公式ドキュメントを参照すること）

```vuejs:src/Components/Includes/Footer.vue

<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>

```

```vuejs:src/Home.vue
<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:２, show: 'true'},
           hash: '#hash',
          }">
          アイテム３
        </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>
```

ページ下部のアイテム３の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（ここでは３）が割り当てられているので/items/3のパスが割り当てられているページへ遷移することになる。
またquery属性に{ item:２, show: 'true'}が指定されているので?item=2&show=trueがURLに付加される。
さらにhash属性に'#hash'が指定されているので#hashがURLに付加されている。

```vuejs:src/Components/Users.vue
<template>
  <div>
    <h2>ユーザー一覧</h2>
    <ul>
      <li><router-link to="/users/1" active-class="active" exact tag="button">ユーザー１</router-link></li>
      <li><router-link to="/users/2" active-class="active" exact tag="button">ユーザー２</router-link></li>
      <li><router-link to="/users/3" active-class="active" exact tag="button">ユーザー３</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()はコンポーネントから離れる前に何らかの処理を行うことが可能。


```vuejs:src/Components/Items.vue
<template>
  <div>
    <h2>アイテム一覧</h2>
    <ul>
      <li><router-link to="/items/1" active-class="active" exact tag="button">アイテム１</router-link></li>
      <li><router-link to="/items/2" active-class="active" exact tag="button">アイテム２</router-link></li>
      <li><router-link to="/items/3" active-class="active" exact tag="button">アイテム３</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ではユーザー１のrouter-link(to="users/1")をクリックした場合にrouter-viewのタグの
場所にUserInfoCompのコンポーネントの内容が描画される。
これはUsers.vueに配置されたrouter-viewにはroutes.jsにおいてUsers.vueのchildrenに指定したコンポーネントのみが描画されるからである。

同様にItems.vueにでアイテム１のrouter-link(to="items/1")をクリックした場合にはrouter-viewタグの場所にはItemInfoCompのコンポーネント内容が描画される。
これはroutes.jsにおいてItems.vueのchildrenにItemInfoCompのコンポーネントが指定されてるからである。

this.$router.push()を使うことによってrouter-linkと同様に画面遷移をすることができる。
引数にはパスを指定する方法とコンポーネントのname属性をオブジェクト形式で指定する方法がある。
（コンポーネントのname属性はsrc/routes.jsで定義している。）


```vuejs:src/Components/Info/UserInfo.vue

<template>
  <div>
    <div>
      ユーザーID：{{ userId }}
    </div>
  </div>
</template>

<script>
export default {
  computed:{
    userId(){
      return this.$route.params.id;
    }
  }
}
</script>

```

```vuejs:src/Components/Info/ItemInfo.vue 
<template>
  <div>
    <div>
      アイテムID：{{ itemNumber }}
    </div>
  </div>
</template>

<script>
export default {
  computed:{
    itemNumber(){
      return this.$route.params.number;
    }
  }
}
</script>

```