Vue.js + LaravelでシンプルなSPA構築チュートリアル：Vueフロントエンド編

# はじめに
Vue.jsとLaravelによるSPA実装のチュートリアル記事です。

本記事は、４本の連載記事の２本目です。

[Vue.js + LaravelでシンプルなSPA構築チュートリアル：概要編](https://qiita.com/minato-naka/items/2d2def4d66ec88dc3ca2)
[Vue.js + LaravelでシンプルなSPA構築チュートリアル：Vueフロントエンド編](https://qiita.com/minato-naka/items/9241d9c7a7433985056d)
↑↑今ここ↑↑
[Vue.js + LaravelでシンプルなSPA構築チュートリアル：LaravelAPI編](https://qiita.com/minato-naka/items/0e709cb0e6628c82c1c5)
[Vue.js + LaravelでシンプルなSPA構築チュートリアル：VueとAPI結合編](https://qiita.com/minato-naka/items/9362ea5af5f823c95b0b)



# 前回まで
前回は、環境構築と必要なパッケージのインストールを行いました。

http://localhost:8000
でLaravelのウェルカムページが表示される状態で
次に進んでください。


# コンポーネントの構成
本記事では、この全体図の青色部分、
Vue.jsによるフロントエンド実装のみを行います。

![Untitled Diagram.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297693/7ffea2ae-b37f-1b23-4be9-b776263c9608.png)



作るページ（コンポーネント）は全部で４つです。

* タスク一覧
* タスク詳細
* タスク登録
* タスク編集


最初に各ページの完成状態の画像を確認します。


* タスク一覧
![list.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297693/11d27433-2eb0-e21e-de2a-253c444a606c.png)


* タスク詳細
![show.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297693/1f55743e-832c-6e2d-2cf0-669484b5be3d.png)


* タスク登録
![create.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297693/caa8f6a1-00e4-0ca5-5841-f6c9f39d77fe.png)


* タスク編集
![edit.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297693/c9342a95-0dc8-57d1-c539-88bace413bd4.png)




前にインストールしたlaravel/ui vueに
デフォルトで組み込まれているbootstrapを使って
最低限のシンプルなUIにしています。
※今回はbootstrapの使い方には言及しません


各ページ上部にある黒い背景色の部分はヘッダーナビで、
全ページ固定で表示されるコンポーネントです。

ヘッダーナビより下の
一覧テーブルや入力フォーム部分が
URLごとに切り替わるメインのコンポーネントになります。


それでは、各ページのメインコンポーネントに加えて
ヘッダーーコンポーネントの
計５つを実装していきます。


# ベースbladeとベースルーティングを追加
このアプリでは、
初回アクセス時のみLaravel側でリクエストを受けて
ページを表示し、
それ以降はフロント側のVue Routerによってルーティングが行われます。

その最初のリクエストを受け取る
Laravel側のルーティングとbladeファイルを追加します。

```diff:routes/web.php
- Route::get('/', function () {
-     return view('welcome');
- });
+ Route::get('/{any}', function() {
+     return view('app');
+ })->where('any', '.*');
```

```diff:resouces/views/app.blade.php
+ <!doctype html>
+ <html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
+ <head>
+     <meta charset="utf-8">
+     <meta name="viewport" content="width=device-width, initial-scale=1">
+ 
+     <!-- CSRF Token -->
+     <meta name="csrf-token" content="{{ csrf_token() }}">
+ 
+     <title>{{ config('app.name', 'Vue Laravel SPA') }}</title>
+ 
+     <!-- Styles -->
+     <link href="{{ mix('/css/app.css') }}" rel="stylesheet">
+ </head>
+ <body>
+ <div id="app">
+ 
+ </div>
+ <!-- Scripts -->
+ <script src="{{ mix('/js/app.js') }}" defer></script>
+ </body>
+ </html>
```

commit：[ベースのbladeとルーティング追加](https://github.com/MinatoNaka/VueLaravelSpa/commit/8b7c9a873ff761bf71078b42517bc683fb0e91b8)

これで、どのURLでアクセスしても
このapp.blade.phpが表示されるようになりました。

また、前回の記事でインストールした
Vue.jsやbootstrapも
`<link href="{{ mix('/css/app.css') }}" rel="stylesheet">`
`<script src="{{ mix('/js/app.js') }}" defer></script>`
このjs、cssファイルで読み込まれているため
利用できる状態です。

試しにデフォルトで用意されている
ExampleComponentを表示してみてください。

```diff:resouces/views/app.blade.php
 <div id="app">
+ <example-component></example-component>
 </div>
```


これで
http://localhost:8000
にアクセスすると、
このようにExampleComponentが表示されると思います。

![example.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297693/7b1b6480-4020-2cc5-33d9-311c621b115f.png)


これが正しく表示されていれば、
Vue.js、bootstrapがちゃんと使えている状態です。
（このExampleComponentはbootstrapが使われています）


# ヘッダーコンポーネント実装
ベースのbladeが配置できたので、
次に全ページ共通で固定表示する
ヘッダーコンポーネントを実装します。

HeaderComponentの追加

```diff:resources/js/components/HeaderComponent.vue
+ <template>
+     <div class="container-fluid bg-dark mb-3">
+         <div class="container">
+             <nav class="navbar navbar-dark">
+                 <span class="navbar-brand mb-0 h1">Vue Laravel SPA</span>
+                 <div>
+                     <button class="btn btn-success">List</button>
+                     <button class="btn btn-success">ADD</button>
+                 </div>
+             </nav>
+         </div>
+     </div>
+ </template>
+ 
+ <script>
+     export default {}
+ </script>
```

classがいろいろとたくさん設定されていますが、
全部bootstrapのclassで見た目を整えているだけなので、
あまり気にしなくてOKです。
　

そのコンポーネントをVueインスタンスに登録

```diff:resources/js/app.js
+ import HeaderComponent from "./components/HeaderComponent";
//↑ファイル先頭

  Vue.component('example-component', require('./components/ExampleComponent.vue').default);
+ Vue.component('header-component', HeaderComponent);
```
　
　

登録したコンポーネントをベースbladeに追加

```diff:resources/views/app.blade.php
 <div id="app">
+     <header-component></header-component>
 </div>
```

commit：[ヘッダーコンポーネント実装](https://github.com/MinatoNaka/VueLaravelSpa/commit/3c52da728a61b7865302eab1ba538f6a00432f74)

この状態でページを表示してみます。
※`npm run dev` または `npm run watch` でソースをビルドするのを忘れないようにしましょう

ページ上部に黒いヘッダーナビが表示されていると思います。

まだボタンのリンク先は設定されていませんが、
この後ページを追加した際にこのボタンのリンクも設定します。

# タスク一覧コンポーネント実装

まずタスク一覧コンポーネントを追加します。

```diff:resources/js/components/TaskListComponent.vue
+ <template>
+     <div class="container">
+         <table class="table table-hover">
+             <thead class="thead-light">
+             <tr>
+                 <th scope="col">#</th>
+                 <th scope="col">Title</th>
+                 <th scope="col">Content</th>
+                 <th scope="col">Person In Charge</th>
+                 <th scope="col">Show</th>
+                 <th scope="col">Edit</th>
+                 <th scope="col">Delete</th>
+             </tr>
+             </thead>
+             <tbody>
+             <tr>
+                 <th scope="row">1</th>
+                 <td>Title1</td>
+                 <td>Content1</td>
+                 <td>Ichiro</td>
+                 <td>
+                     <button class="btn btn-primary">Show</button>
+                 </td>
+                 <td>
+                     <button class="btn btn-success">Edit</button>
+                 </td>
+                 <td>
+                     <button class="btn btn-danger">Delete</button>
+                 </td>
+             </tr>
+             <tr>
+                 <th scope="row">2</th>
+                 <td>Title2</td>
+                 <td>Content2</td>
+                 <td>Jiro</td>
+                 <td>
+                     <button class="btn btn-primary">Show</button>
+                 </td>
+                 <td>
+                     <button class="btn btn-success">Edit</button>
+                 </td>
+                 <td>
+                     <button class="btn btn-danger">Delete</button>
+                 </td>
+             </tr>
+             <tr>
+                 <th scope="row">3</th>
+                 <td>Title3</td>
+                 <td>Content3</td>
+                 <td>Saburo</td>
+                 <td>
+                     <button class="btn btn-primary">Show</button>
+                 </td>
+                 <td>
+                     <button class="btn btn-success">Edit</button>
+                 </td>
+                 <td>
+                     <button class="btn btn-danger">Delete</button>
+                 </td>
+             </tr>
+             </tbody>
+         </table>
+     </div>
+ </template>
+ 
+ <script>
+     export default {}
+ </script>
```

ID、Title、Content（内容）、Person In Charge（担当者）、各種操作ボタン
をカラムに持つテーブルです。

現時点では、サンプルとして３行ほどべた書きで
タスクを表示しています。

後々の作業でここは
LaravelAPIからデータを受け取り表示するようになります。


また、
Show、Edit、Deleteのボタンを設置していますが
いまはリンク先が設定されていません。

後々各コンポーネントを実装したらリンク先を設定していきます。
　
　
追加したタスク一覧コンポーネントを
Vue Routerに登録します。

```diff:resources/js/app.js
+ import VueRouter from 'vue-router';
  import HeaderComponent from "./components/HeaderComponent";
+ import TaskListComponent from "./components/TaskListComponent";


  window.Vue = require('vue');


+ Vue.use(VueRouter);
+ 
+ const router = new VueRouter({
+     mode: 'history',
+     routes: [
+         {
+             path: '/tasks',
+             name: 'task.list',
+             component: TaskListComponent
+         },
+     ]
+ });


  const app = new Vue({
      el: '#app',
+     router
  });
```

VueRouter自体の詳しい解説は省略しますが、
ポイントはここです。

```js
routes: [
    {
        path: '/tasks',
        name: 'task.list',
        component: TaskListComponent
    },
]
```

ここで、
「/tasks」のURLでアクセスしたら
「TaskListComponent」を表示する。
このルーティングの名前は「task.list」である。
と設定しています。

別ページ（コンポーネント）を追加した際は、
同じようにこの `routes` に設定を加えていくことになります。
　
　

そして、ルーティングで紐づけられたコンポーネントを表示するために、
ベースのbladeに `<router-view>` を配置する必要があります。

```diff:resources/views/app.blade.php
  <div id="app">
     <header-component></header-component>


+    <router-view></router-view>
  </div>
```

先ほどVue Routerで設定したとおり、
URLに紐づくコンポーネントがこの
`<router-view>` の部分に表示されることになります。

この状態で
http://localhost:8000/tasks
にアクセスしてみましょう。
※ビルドを忘れずに

お手本で見た通りの
一覧テーブルが表示されていると思います。


ついでに、
ヘッダーコンポーネントにある
「List」ボタンのリンク先を設定しておきましょう。

```diff:resources/js/components/HeaderComponent.vue

<nav class="navbar navbar-dark">
    <span class="navbar-brand mb-0 h1">Vue Laravel SPA</span>
    <div>
        <button class="btn btn-success">List</button>
+        <router-link v-bind:to="{name: 'task.list'}">
            <button class="btn btn-success">List</button>
+        </router-link>
        <button class="btn btn-success">ADD</button>
    </div>

</nav>
```

このように `<route-link>` の `v-bind:to` で
リンク先のルーティング名を設定することで
SPAのリンクとして動作させることができます。

commit：[タスク一覧コンポーネント実装](https://github.com/MinatoNaka/VueLaravelSpa/commit/fa1caf1cb694ddda1d2994e331e614dc7f3b1b65)


# タスク詳細コンポーネント実装
次に、タスク詳細コンポーネントを追加します。

まずコンポーネントファイル作成。

```diff:resources/js/components/TaskShowComponent.vue
+ <template>
+     <div class="container">
+         <div class="row justify-content-center">
+             <div class="col-sm-6">
+                 <form>
+                     <div class="form-group row border-bottom">
+                         <label for="id" class="col-sm-3 col-form-label">ID</label>
+                         <input type="text" class="col-sm-9 form-control-plaintext" readonly id="id"
+                                v-bind:value="taskId">
+                     </div>
+                     <div class="form-group row border-bottom">
+                         <label for="title" class="col-sm-3 col-form-label">Title</label>
+                         <input type="text" class="col-sm-9 form-control-plaintext" readonly id="title"
+                                value="title title">
+                     </div>
+                     <div class="form-group row border-bottom">
+                         <label for="content" class="col-sm-3 col-form-label">Content</label>
+                         <input type="text" class="col-sm-9 form-control-plaintext" readonly id="content"
+                                value="content content">
+                     </div>
+                     <div class="form-group row border-bottom">
+                         <label for="person-in-charge" class="col-sm-3 col-form-label">Person In Charge</label>
+                         <input type="text" class="col-sm-9 form-control-plaintext" readonly id="person-in-charge"
+                                value="Ichiro">
+                     </div>
+                 </form>
+             </div>
+         </div>
+     </div>
+ </template>
+ 
+ <script>
+     export default {
+         props: {
+             taskId: String
+         }
+     }
+ </script>
```

taskIdをURLパラメータとして受け取って、
そのIDのみ
`<input type="text" class="col-sm-9 form-control-plaintext" readonly id="id" v-bind:value="taskId">`
の `v-bind:value="taskId"` 部分で動的に表示しています。

それ以外のcontent、person-in-chargeは
まだべた書きにしているだけです。

このコンポーネントをVue Routerに登録します。

```diff:resources/js/app.js
import VueRouter from 'vue-router';
import HeaderComponent from "./components/HeaderComponent";
import TaskListComponent from "./components/TaskListComponent";
+ import TaskShowComponent from "./components/TaskShowComponent";


{
    path: '/tasks',
    name: 'task.list',
    component: TaskListComponent
},

+ {
+     path: '/tasks/:taskId',
+     name: 'task.show',
+     component: TaskShowComponent,
+     props: true
+ },
```


これで、`/tasks/:taskId` のURLでアクセスすると、
TaskShowComponentが表示されます。

`:taskId` の部分は、任意のタスクIDが入ります。

このURLパラメータが、
先ほどのタスク詳細コンポーネントの中で使われていた
`taskId` となります。


http://localhost:8000/tasks/3
のように `:taskId` の部分に好きな数字を入れてアクセスすると
タスク詳細コンポーネントが表示されます。


ついでにタスク一覧コンポーネントに置いていた
「Show」ボタンのリンク先を設定しておきましょう。

```diff:resources/js/components/TaskListComponent.vue


+    <router-link v-bind:to="{name: 'task.show', params: {taskId: 1}}">
        <button class="btn btn-primary">Show</button>
+    </router-link>


+    <router-link v-bind:to="{name: 'task.show', params: {taskId: 2}}">
        <button class="btn btn-primary">Show</button>
+    </router-link>


+    <router-link v-bind:to="{name: 'task.show', params: {taskId: 3}}">
        <button class="btn btn-primary">Show</button>
+    </router-link>
```

これで、一覧ページの「Show」ボタンをクリックすると
タスク詳細ページに遷移するようになりました。

commit：[タスク詳細コンポーネント実装](https://github.com/MinatoNaka/VueLaravelSpa/commit/1118a6eaf12bf937c43311224620e034f7febdb7)


# タスク登録コンポーネント実装
次にタスク登録コンポーネントを実装します。

まずコンポーネントファイル作成。

```diff:resources/js/components/TaskCreateComponent.vue
+ <template>
+     <div class="container">
+         <div class="row justify-content-center">
+             <div class="col-sm-6">
+                 <form>
+                     <div class="form-group row">
+                         <label for="title" class="col-sm-3 col-form-label">Title</label>
+                         <input type="text" class="col-sm-9 form-control" id="title">
+                     </div>
+                     <div class="form-group row">
+                         <label for="content" class="col-sm-3 col-form-label">Content</label>
+                         <input type="text" class="col-sm-9 form-control" id="content">
+                     </div>
+                     <div class="form-group row">
+                         <label for="person-in-charge" class="col-sm-3 col-form-label">Person In Charge</label>
+                         <input type="text" class="col-sm-9 form-control" id="person-in-charge">
+                     </div>
+                     <button type="submit" class="btn btn-primary">Submit</button>
+                 </form>
+             </div>
+         </div>
+     </div>
+ </template>
+ 
+ <script>
+     export default {}
+ </script>
```

ただ空のフォームを表示しているだけです。
現時点では送信処理は書いていません。


このコンポーネントをVue Routerに登録します。

```diff:resources/js/app.js
import VueRouter from 'vue-router';
import HeaderComponent from "./components/HeaderComponent";
import TaskListComponent from "./components/TaskListComponent";
+ import TaskCreateComponent from "./components/TaskCreateComponent";
import TaskShowComponent from "./components/TaskShowComponent";


{
    path: '/tasks',
    name: 'task.list',
    component: TaskListComponent
},

+ {
+     path: '/tasks/create',
+     name: 'task.create',
+     component: TaskCreateComponent
+ },
{
    path: '/tasks/:taskId',
    name: 'task.show',
    component: TaskShowComponent,
    props: true
},
```

これで、
http://localhost:8000/tasks/create
でアクセスすればタスク登録ページが表示されます。


ついでにヘッダーコンポーネントに置いていた
「Add」ボタンのリンク先を設定しておきます。


```diff:resources/js/components/HeaderComponent.vue

<div>
    <router-link v-bind:to="{name: 'task.list'}">
        <button class="btn btn-success">List</button>
    </router-link>
+    <router-link v-bind:to="{name: 'task.create'}">
        <button class="btn btn-success">ADD</button>
+    </router-link>

</div>
```

commit：[タスク登録コンポーネント実装](https://github.com/MinatoNaka/VueLaravelSpa/commit/07030b8c994d75e45ab40c0330cc7bab4fea6916)


# タスク編集コンポーネント実装
次に、タスク編集コンポーネントを実装します。

まずコンポーネントファイルを作成。

```diff:resources/js/components/TaskEditComponent.vue
+ <template>
+     <div class="container">
+         <div class="row justify-content-center">
+             <div class="col-sm-6">
+                 <form>
+                     <div class="form-group row">
+                         <label for="id" class="col-sm-3 col-form-label">ID</label>
+                         <input type="text" class="col-sm-9 form-control-plaintext" readonly id="id" v-bind:value="taskId">
+                     </div>
+                     <div class="form-group row">
+                         <label for="title" class="col-sm-3 col-form-label">Title</label>
+                         <input type="text" class="col-sm-9 form-control" id="title">
+                     </div>
+                     <div class="form-group row">
+                         <label for="content" class="col-sm-3 col-form-label">Content</label>
+                         <input type="text" class="col-sm-9 form-control" id="content">
+                     </div>
+                     <div class="form-group row">
+                         <label for="person-in-charge" class="col-sm-3 col-form-label">Person In Charge</label>
+                         <input type="text" class="col-sm-9 form-control" id="person-in-charge">
+                     </div>
+                     <button type="submit" class="btn btn-primary">Submit</button>
+                 </form>
+             </div>
+         </div>
+     </div>
+ </template>
+ 
+ <script>
+     export default {
+         props: {
+             taskId: String
+         }
+     }
+ </script>
```


詳細ページと同様に、
`taskId` をURLパラメータで受け取り、
IDの欄にデータを表示しています。


このコンポーネントをVue Routerに登録します。

```diff:resources/js/app.js
import TaskCreateComponent from "./components/TaskCreateComponent";
import TaskShowComponent from "./components/TaskShowComponent";
+ import TaskEditComponent from "./components/TaskEditComponent";


{
    path: '/tasks',
    name: 'task.list',
    component: TaskListComponent
},
{
    path: '/tasks/create',
    name: 'task.create',
    component: TaskCreateComponent
},
{
    path: '/tasks/:taskId',
    name: 'task.show',
    component: TaskShowComponent,
    props: true
},

+ {
+     path: '/tasks/:taskId/edit',
+     name: 'task.edit',
+     component: TaskEditComponent,
+     props: true
+ },
```

これで、
http://localhost:8000/tasks/:taskId/edit
にアクセスするとタスク編集ページが表示されます。

`:taskId` の部分は任意のタスクIDになります。


ついでにタスク一覧コンポーネントに置いていた
「Edit」ボタンのリンク先も設定しておきます。


```diff:resources/js/components/TaskListComponent.vue


+    <router-link v-bind:to="{name: 'task.edit', params: {taskId: 1}}">
        <button class="btn btn-success">Edit</button>
+    </router-link>


+    <router-link v-bind:to="{name: 'task.edit', params: {taskId: 2}}">
        <button class="btn btn-success">Edit</button>
+    </router-link>


+    <router-link v-bind:to="{name: 'task.edit', params: {taskId: 3}}">
        <button class="btn btn-success">Edit</button>
+    </router-link>
```


commit：[タスク編集コンポーネント実装](https://github.com/MinatoNaka/VueLaravelSpa/commit/89d82c23dad7cc6298aa9b685a71a4a5253f07ae)


# おわりに
これで、
・タスク一覧ページ
・タスク詳細ページ
・タスク登録ページ
・タスク編集ページ
が実装できました。

現時点ではAPIでデータを取得する処理はできていませんが、
この状態でもVue.jsによる **静的な** SPAにはなっています。


もしデータベースを利用しないような
ウェブサイトなどをVue.jsでSPAとして構築する場合は
今回解説した内容を基本として
ページの追加をしていくだけです。



それでは、次にLaravelのAPI実装に進みましょう。
（次に進む前に、LGTMしてもらえるとうれしいです）
[Vue.js + LaravelでシンプルなSPA構築チュートリアル：LaravelAPI編](https://qiita.com/minato-naka/items/0e709cb0e6628c82c1c5)