273
166

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

AsiaQuestAdvent Calendar 2019

Day 18

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

Last updated at Posted at 2019-12-17

はじめに

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

本記事は、4本の連載記事の2本目です。

Vue.js + LaravelでシンプルなSPA構築チュートリアル:概要編
Vue.js + LaravelでシンプルなSPA構築チュートリアル:Vueフロントエンド編
↑↑今ここ↑↑
Vue.js + LaravelでシンプルなSPA構築チュートリアル:LaravelAPI編
Vue.js + LaravelでシンプルなSPA構築チュートリアル:VueとAPI結合編

前回まで

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

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

コンポーネントの構成

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

Untitled Diagram.png

作るページ(コンポーネント)は全部で4つです。

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

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

  • タスク一覧
    list.PNG

  • タスク詳細
    show.PNG

  • タスク登録
    create.PNG

  • タスク編集
    edit.PNG

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

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

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

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

ベースbladeとベースルーティングを追加

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

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

routes/web.php
- Route::get('/', function () {
-     return view('welcome');
- });
+ Route::get('/{any}', function() {
+     return view('app');
+ })->where('any', '.*');
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とルーティング追加

これで、どの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を表示してみてください。

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

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

example.PNG

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

ヘッダーコンポーネント実装

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

HeaderComponentの追加

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インスタンスに登録

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

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

 
 

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

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

commit:ヘッダーコンポーネント実装

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

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

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

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

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

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(担当者)、各種操作ボタン
をカラムに持つテーブルです。

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

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

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

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

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自体の詳しい解説は省略しますが、
ポイントはここです。

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

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

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

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

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」ボタンのリンク先を設定しておきましょう。

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:タスク一覧コンポーネント実装

タスク詳細コンポーネント実装

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

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

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に登録します。

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」ボタンのリンク先を設定しておきましょう。

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:タスク詳細コンポーネント実装

タスク登録コンポーネント実装

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

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

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に登録します。

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」ボタンのリンク先を設定しておきます。

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:タスク登録コンポーネント実装

タスク編集コンポーネント実装

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

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

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に登録します。

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」ボタンのリンク先も設定しておきます。

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:タスク編集コンポーネント実装

おわりに

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

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

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

それでは、次にLaravelのAPI実装に進みましょう。
(次に進む前に、LGTMしてもらえるとうれしいです)
Vue.js + LaravelでシンプルなSPA構築チュートリアル:LaravelAPI編

273
166
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
273
166

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?