Vue Router を使って簡単に SPA 構築

## 前提
端末: MacBook Pro
OS: macOS Catalina 10.15.7
Node.js: v12.18.3
Vue.js: v2.6.11
Vue-CLI: v4.5.13

Node.js, yarn, Vue 既にインストール済み
Vue-CLI を利用して、プロジェクトを作成済み

## Vue Router とは
Vue.js を利用した SPA（シングルページアプリケーション）の構築において、ルーティング制御をするための公式プラグインのことを指す

## SPA とは
複数ページではなく、単一のページで構成された web アプリケーションまたは web サイトのことを指す
初めに 1 つの html を読み込み、以後は Ajax で情報を動的にページを更新させる
新しい情報を読み込む際にページ遷移を行う必要がないため、ネイティブアプリのような動作をすることが可能

## セットアップ
yarn でインストール
（Vue Cli でプロジェクト作成した場合だと、既にインストール済みの可能性あり）

```terminal
$ yarn add vue-router
```

package.json に定義されているか確認

```package.json
{
  "dependencies": {
    "core-js": "^3.6.5",
    "vue": "^2.6.11",
    "vue-router": "^3.5.1"
  },
}
```

## 簡単に SPA を作成する
router.js を /src 配下に作成する
（表示用のコンポーネントは適時作成）

```router.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from './components/Home'
import Foo from './components/Foo'
import Bar from './components/Bar'

// モジュールとして使用する場合に必要
Vue.use(VueRouter)

// ルーターインスタンスを作成
export default new VueRouter({
  // デフォルトの挙動では url に # が含まれている（例: ~/#/foo）
  // history モードを指定することで url から hash を取り除くことが可能
  mode: 'history',

  // ルート定義において、各ルートは 1 つのコンポーネントとマッピングされる必要がある
  // 適時コンポーネント作成をお願いします => <template><div>HOME</div></template>
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    },

    {
      path: '/foo',
      name: 'foo',
      component: Foo
    },

    {
      path: '/bar',
      name: 'bar',
      component: Bar
    },
  ]
})
```

main.js を修正

```main.js
import Vue from 'vue'
import App from './App.vue'
// 3 で作成した router モジュール
import router from './router'

Vue.config.productionTip = false

// root となる Vue インスタンスを作成する際にルーターを定義する
// 他のコンポーネントで this.$router と定義をすることで VueRouter にアクセス可能となる
new Vue({
  router,
  render: h => h(App),
}).$mount('#app')
```

App.vue の template の中身を修正

```App.vue
<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
    <h1>Hello Vue.js Hands on!</h1>

    <div>
      <h2>SPA</h2>
      <!-- SPA を実現させるためには router-link コンポーネントを使用する -->
      <!-- リンク先を to プロパティに指定する -->
      <!-- router-link タグは a タグとしてレンダリングされる -->
      <router-link to="/">Go to Home</router-link><br>
      <router-link to="/foo">Go to Foo</router-link><br>
      <router-link to="/bar">Go to Bar</router-link>
    </div>

    <div>
      <h2>NOT SPA</h2>

      <a href="/">Go to Home</a><br>
      <a href="/foo">Go to Foo</a><br>
      <a href="/bar">Go to Bar</a>
    </div>

    <div>
      <h2>CONTENTS</h2>
      <!-- ルートとマッチしたコンポーネントが描画される -->
      <router-view />
    </div>
  </div>
</template>
```

下記のコマンドを叩いてローカルサーバーを起動

```terminal
$ yarn serve
```

![vuejs_hands_on_SPA_HOME.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/441218/16d8456a-6719-a4a0-3170-5c27b6b5fa45.png)

SPA の a リンクを押下しても、リロードが走らない（SPA）ことが確認可能
NOT SPA の a リンクをクリックすると通常のリクエスト、レスポンスとなり、リロードが走る

## ルーターへのアクセス

`this.$route` でルーティング情報にアクセスすることが可能
`this.$router` でルーティング全体の情報にアクセスすることが可能
下記の処理は、 js の処理内で使用することが多い

```javascript
// router-link で定義した a リンクをクリックすることと同義
this.$router.push('/home')
```

## 動的ルートマッチング

router.js に下記のパス情報を追加する

```router.js
{
  // 「:~」と定義することで、パラメーターを含むルーティングを設定可能
  path: '/user/:id',
  name: 'user',
  component: User
}
```

作成した User.vue に定義する

``` User.vue
<template>
  <div>
    USER<br>
    <!-- url が含むパラメーターは $route.params.~ で取得可能 -->
    {{ $route.params.id }}
  </div>
</template>
```

## 名前付きルート

router.js における routes オプションの中でルートに名前を付与することが可能

```router.js
routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    },

    {
      path: '/foo',
      name: 'foo',
      component: Foo
    },

    {
      // 「:~」と定義することで、パラメーターを含むルーティングを設定可能
      path: '/user/:id',
      name: 'user',
      component: User
    }
 ]
```

router-link コンポーネントの to プロパティにオブジェクトを渡すことが可能 

```javascript
<router-link :to="{ name: 'user', params: { id: 123 }}">Go to User 123</router-link>
```

## リダイレクト

router.js における routes の設定にてリダイレクトが可能
下記の記述で、 /foo から /bar にリダイレクトさせる

```router.js
routes: [
  {
    path: '/foo',
    redirect: '/bar',
  }
]
```

上記で定義した名前付きルートに対してもリダイレクトが可能

```router.js
routes: [
  {
    path: '/foo',
    redirect: { name: 'bar' },
  }
]
```

function を使用した動的なダイレクトも可能

```router.js
routes: [
  {
    path: '/foo',
    redirect: () => {
      alert('リダイレクトします')
      return '/bar'
    },
]
```

## ネストされたルーティング

下記のようなルーティングだった場合の実現方法

```javascript
~/user/:id
~/user/:id/profile
~/user/:id/posts
```

router.js を修正する

```router.js
{
  path: '/user/:id',
  name: 'user',
  component: User,
  children: [
    {
      // ~/user/:id/profile がマッチした際に User コンポネ内の router-view 内部で描画される
      path: 'profile',
      component: UserProfile
    },
    {
      // ~/user/:id/posts がマッチした際に User コンポネ内の router-view 内部で描画される
      path: 'posts',
      component: UserPosts
    }
  ]
}
```

User.vue を修正する

```User.vue
<template>
  <div>
    USER
    <!-- ネストされたコンポネは下記の router-view にて描画される -->
    <router-view />
  </div>
</template>
```

## スクロール位置を制御する

デフォルトだと SPA でページを表示した際に、スクロール位置が固定されている。
VueRouter のインスタンスを生成する際に下記の定義を追加することで、
ブラウザらしい挙動になる。

```router.js
export default new VueRouter({
  .
  .
  scrollBehavior (to, from, savedPosition) {
    // ブラウザの「戻る/進む」ボタン押下時には、スクロール位置が固定される（savedPostion に定義されている）
    return savedPosition ? savedPosition : { x: 0, y: 0 }
  },
  .
  .
 }
```