Vue.js 3 入門 「ルーティング」（Vue Router）

# はじめに

Vue.js 3 の ルーティング について、自分が学んだことを備忘録として記載します。
Vue.js に殆ど触れたことが無い方に少しでも参考になれば幸いです。
誤り等あれば、ご指摘頂けますと大変喜びます

# プロジェクトの作成

まずは Vue CLI を用いてプロジェクトを作成します。
Vue CLI については[こちらの記事](https://qiita.com/yagi405/items/4e498ef424b92d9d38d0)を参照してください。

プロジェクトを作成するには、作成したいフォルダで以下のコマンドを実行します。
`hello-router`はプロジェクト名です。任意のプロジェクト名を設定してください。

```
cd 任意のフォルダ
vue create hello-router
```

## プリセットの選択

すると、以下のように利用するプリセット（プロジェクト設定）の選択を求められます。
まずは最低限の構成とするので「Manually select features」（手動で選択）を選択します。
versionはご自身のバージョンに読み替えてください。

```
Vue CLI v4.5.9
? Please pick a preset:
  Default ([Vue 2] babel, eslint)
  Default (Vue 3 Preview) ([Vue 3] babel, eslint)
> Manually select features    
```

## プロジェクトに組み込むモジュールを選択

プロジェクトに組み込むモジュールを選択します。
ここで`Babel`と`Linter`に加えて、`Router`を選択します。
[Space]キーで選択することができ、[Enter]キーで確定となります。

`Router`を選択することによって、`Vue Router`というルーティング機能を提供するライブラリが組み込まれます。

```
Vue CLI v4.5.9
? Please pick a preset: Manually select features
? Check the features needed for your project:
 (*) Choose Vue version
 (*) Babel
 ( ) TypeScript
 ( ) Progressive Web App (PWA) Support
>(*) Router
 ( ) Vuex
 ( ) CSS Pre-processors
 (*) Linter / Formatter
 ( ) Unit Testing
 ( ) E2E Testing                                                                                                                                                                                                                                                                          
```

## Vue.js のバージョンを選択

Vue.js のバージョンを選択します。
本記事では 3.x を選択します。

```
Vue CLI v4.5.9
? Please pick a preset: Manually select features
? Check the features needed for your project: Choose Vue version, Babel, Router, Linter
? Choose a version of Vue.js that you want to start the project with
  2.x
> 3.x (Preview)                                                                                                                                                                                              
```

# Historyモードの有効/無効を選択

Historyモードの有効/無効を選択します。
基本的に`Y`（有効）で問題ありません。

```
Vue CLI v4.5.9
? Please pick a preset: Manually select features
? Check the features needed for your project: Choose Vue version, Babel, Router, Linter
? Choose a version of Vue.js that you want to start the project with 3.x (Preview)
? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n) Y   
```

## Linter の設定を選択

`Linter` の設定を選択します。
今回は最低限の`ESLint with error prevention only`（エラー防止のみ）を選択します。

```
Vue CLI v4.5.9
? Please pick a preset: Manually select features
? Check the features needed for your project: Choose Vue version, Babel, Router, Linter
? Choose a version of Vue.js that you want to start the project with 3.x (Preview)
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a linter / formatter config: (Use arrow keys)
> ESLint with error prevention only
  ESLint + Airbnb config
  ESLint + Standard config
  ESLint + Prettier                                                                                                                                                                                                                                                                                                          
```

続けて、Lintの実行タイミングの選択を求められます。
`Lint on save`（保存時）を選択します。

```
Vue CLI v4.5.9
? Please pick a preset: Manually select features
? Check the features needed for your project: Choose Vue version, Babel, Router, Linter
? Choose a version of Vue.js that you want to start the project with 3.x (Preview)
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a linter / formatter config: Basic
? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)
>(*) Lint on save
 ( ) Lint and fix on commit      
```

## 設定情報の格納先を選択

`Babel`と`ESLint`の設定情報を個別の設定ファイルとするか、`package.json`にまとめるかを選択します。
個別の設定ファイルとしたほうが綺麗なので`In dedicated config files`を選択します。

```
Vue CLI v4.5.9
? Please pick a preset: Manually select features
? Check the features needed for your project: Choose Vue version, Babel, Router, Linter
? Choose a version of Vue.js that you want to start the project with 3.x (Preview)
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a linter / formatter config: Basic
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys)
> In dedicated config files
  In package.json 
```

## 今回の設定を保存しておくかを選択

今回の設定を保存しておくかを選択します。
今回はあくまでお試しなので`N`（保存しない）とします。

```
Vue CLI v4.5.9
? Please pick a preset: Manually select features
? Check the features needed for your project: Choose Vue version, Babel, Router, Linter
? Choose a version of Vue.js that you want to start the project with 3.x (Preview)
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a linter / formatter config: Basic
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? (y/N) N                                                                                                                         
```                    

## プロジェクトの生成開始

ここまでの設定内容を元に、プロジェクトの生成が開始されるので、完了するまで待機します。
正常に完了すると、以下のような文言が表示されます。

```
Vue CLI v4.5.9
Creating project in 任意のフォルダ\hello-router.
Installing CLI plugins. This might take a while...

途中省略...

Running completion hooks...

Generating README.md...

Successfully created project hello-router.
Get started with the following commands:

 $ cd hello-router
 $ npm run serve
```

## 生成されたフォルダを確認

カレントフォルダに、指定したプロジェクト名のフォルダが生成されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544508/f600a06c-a14a-ca18-cc45-6927d545f402.png)

# アプリの実行

早速実行してみましょう。
上記のプロジェクト生成完了時の文言（Get started with the following commands:）にある通り、以下のコマンドを実行します。
プロジェクトルートに移動して、開発用のサーバーを実行するコマンドです。

```
cd hello-router
npm run serve
```

以下のような文言が表示されれば、開発用のサーバーが起動できています。
ブラウザを起動し`http://localhost:8080`にアクセスしてください。

```
  App running at:

途中省略...

  Note that the development build is not optimized.
  To create a production build, run npm run build.
```

# ページの表示

以下のような画面が表示されれば、プロジェクトの作成は成功です。
開発用サーバーは[Ctrl] + [C]で終了することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544508/61afedf3-502f-6df0-d193-308b4bdbf06c.png)

# 挙動を確認する

ここからルーティングの挙動を確認していきます。
`http://localhost:8080`にアクセスすると、以下のようにHome画面が表示されます。
つまり、`Home`コンポーネント（`/src/views/Home.vue`）が呼び出されていることがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544508/1d0c00ee-7424-39e2-3724-2c110e89be5f.png)

ここでアドレスバーから手入力でURLを変更します。
変更前は以下のようになっているかと思います。

```
http://localhost:8080
```

アドレスバーから手入力でURLを以下のように変更してください。
末尾に`/about`を追加します。

```
http://localhost:8080/about
```

変更後、Enterを押下すると、以下のようにAbout画面が表示されると思います。
つまり、`About`コンポーネント（`/src/views/About.vue`）が呼び出されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544508/58e7b928-3299-bb3b-c64e-adea92bc8dd9.png)

ここで生じた2つの疑問について、仕組みを説明していきます。

* `http://localhost:8080`にアクセスすると、**なぜ`Home`コンポーネント（`/src/views/Home.vue`）が呼び出されるのか**
* `/about`を追加すると、**なぜ`About`コンポーネント（`/src/views/About.vue`）が呼び出されるのか**

# ルーティング

クライアントから要求されたURLに応じて、処理を受け渡すコンポーネントを決定する仕組みのことです。
ルーティング機能を提供するライブラリのことをルーターと呼び、Vue.js では標準的なルーターとして`Vue Router`が用意されています。

# ルーティングの流れ

リクエストのURLをアプリケーションが定義しているルート（Route）と照合します。
一致するルートが見つかった場合は、該当するコンポーネントが呼び出されます。

```
http://localhost:8080/about
```

従って`About`コンポーネントが呼び出されるのは、上記の**URLをルートと照合した結果、一致するルートが見つかったから**ということになります。

# ルート
では、ルートはどこでどのように定義されているのでしょうか。
ルーティング情報として`/src/route/index.js`に定義されています。

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

まず抑えておきたいのは`createRouter`メソッドによって、ルーティング情報を扱うルーターが生成されている点です。

```js
const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})
```

### createRouterメソッド

* **history**
    * historyモードの基本情報
    * ほぼ定型
        - `createWebHistory(process.env.BASE_URL)`
* **routes**
    * **ルート**
    * ここでは`routes`変数の値が割り当てられている

実際のルートが定義されているのは、`routes`変数です。

```js
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')
  }
]
```

* **path**
    * リクエストパス
* **name**
    * ルートの名前
* **component**
    * ルーティングによって呼び出されるコンポーネント
 
従って、ここでは以下の2つのルートが定義されていることになります。

* `Home`ルート
`/`で`Home`コンポーネントが呼び出される

* `About`ルート
`/about`で`About`コンポーネントが呼び出される

結果として、`http://localhost:8080`にアクセスすると`Home`コンポーネントが呼び出され、`/about`を追加すると、`About`コンポーネントが呼び出されることになります。

# ルーターの有効化

なお、ルーターは`/src/main.js`で有効化されています。

```js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

createApp(App).use(router).mount('#app')
```

Vueインスタンスにライブラリを組み込む`use`メソッドに、定義したルーター（`router`）を渡すことで有効化しています。


# ルートを追加してみる

## コンポーネントを追加

### `src/views/Article.vue`を追加

`src/views`フォルダには、ルーティングに関わるコンポーネントを配置します。
`src/components`フォルダも同様の役割ですが、こちらにはより細かい部品を配置します。

```html
<template>
    <div class="article">
        <h1>This is an article page</h1>
    </div>
</template>
```

## ルートを追加

### `src/router/index.js`を以下のように修正

`Article`ルートを追加します。

```js
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')
  },
  // ↓ 追加
  {
    path: '/article',
    name: 'Article',
    component: () => import('../views/Article.vue')
  }
]
```

## ついでにTOPのリンクも追加

### `src/App.vue`を以下のように修正

ルーター経由のリンクは、`a`タグではなく、`router-link`を利用します（`to`属性でリンク先を指定）。
また、ルーター経由で呼び出されたコンポーネントは、`router-view`の領域に反映されます。
※ルーターを利用する場合、`router-view`による表示領域の確保は必須

```html
<template>
  <div id="nav">
    <router-link to="/">Home</router-link> |
    <router-link to="/about">About</router-link> |
    <router-link to="/article">Article</router-link> <!-- 追加 -->
  </div>
  <router-view/>
</template>
```

## 結果を確認

### 以下のURLにアクセス

```
http://localhost:8080
```

Articleページへのリンクが追加されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544508/0a05b4f5-5435-0b11-6ca1-f510d31c1ddd.png)

### 以下のURLにアクセス または `Article`のリンクを押下

```
http://localhost:8080/article
```

`Article`コンポーネントが呼び出されていることが確認できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544508/9b20e497-c78d-e823-4eaa-f13cc1cd91f6.png)

以上となります。
ありがとうございました。