2019.01.12

Vue + Vue Router + Vuex + Laravelで写真共有アプリを作ろう (5) 認証ページ


この連載記事では、フロントエンドに Vue.js + Vue Router + Vuex とサーバーサイドに Laravel を使用したシングルページ Web アプリケーションの開発方法を紹介します。実際に写真共有アプリを開発する手順を通して SPA 開発のエッセンスを学んでいただけるように書いていきます。

今回のチュートリアルで扱うツールなどのバージョンは以下の通りです。

Node npm Vue.js Vue Router Vuex PHP Laravel
10.7.0 6.4.1 2.5.21 3.0.2 3.0.1 7.2.10 5.7.19

この章ではログイン画面のユーザーインターフェースを実装します。
基本的な Vue コンポーネントの機能を使ってページを構築する方法を学びましょう。

ヘッダーとフッター

まずはログイン画面へのリンクを配置するヘッダーとフッターを追加しましょう。

ヘッダーコンポーネント

resources/js/componentsNavbar.vue を作成してください。

Navbar.vue
<template>
  <nav class="navbar">
    <RouterLink class="navbar__brand" to="/">
      Vuesplash
    </RouterLink>
    <div class="navbar__menu">
      <div class="navbar__item">
        <button class="button">
          <i class="icon ion-md-add"></i>
          Submit a photo
        </button>
      </div>
      <span class="navbar__item">
        username
      </span>
      <div class="navbar__item">
        <RouterLink class="button button--link" to="/login">
          Login / Register
        </RouterLink>
      </div>
    </div>
  </nav>
</template>

このコンポーネントはヘッダーのナビゲーションバーです。

RouterLink コンポーネントが初めて登場しています。

RouterLinkRouterView と同様、Vue Router から提供されているコンポーネントです。役割はアンカーリンクとほぼ同じで HTML 的にも <a> 要素が描画されるのですが、普通のアンカーリンクと挙動が大きく異なるのは通常の画面遷移(=サーバサイドへの GET リクエスト)が発生しないことです。RouterLink で描画したリンクをクリックすると、フロントエンドでの画面遷移、つまり Vue Router によるコンポーネントの切り替わりが発生します。

サーバサイドへの通信回数を最適化して SPA の利点を活かすため、Vue Router の管理下にあるページへ遷移する場合は必ず RouterLink を使用しましょう。

また、写真投稿ボタンとユーザー名表示箇所も表示されています。こちらは後続の章で認証状態による切り替え処理を追加します。つまりログイン済みであれば写真投稿ボタンとユーザー名を表示し、ログインしていなければログインリンクを表示します。

フッターコンポーネント

続いてフッターコンポーネントです。
resources/js/componentsFooter.vue を作成してください。

Footer.vue
<template>
  <footer class="footer">
    <button class="button button--link">Logout</button>
    <RouterLink class="button button--link" to="/login">
      Login / Register
    </RouterLink>
  </footer>
</template>

ログアウトボタンとログインページへのリンクが両方表示されていますが、ここには後ほど認証状態による切り替え処理を追加します。

App.vue

ルートコンポーネントである App.vueNavibarFooter を使用します。

App.vue
<template>
  <div>
    <header>
      <Navbar />
    </header>
    <main>
      <div class="container">
        <RouterView />
      </div>
    </main>
    <Footer />
  </div>
</template>

<script>
import Navbar from './components/Navbar.vue'
import Footer from './components/Footer.vue'

export default {
  components: {
    Navbar,
    Footer
  }
}
</script>

ビルドコマンドを実行していなければ、実行してください。

$ npm run watch

ブラウザでヘッダーとフッターが表示されることを確認しましょう。

タブ機能を実装する

ここからログインページの UI を実装していきます。
まずはログインフォームと会員登録フォームを切り替えるタブを作成します。

タブ UI を追加する

resources/js/pages/Login.vue を以下の通りに編集してください。

Login.vue
<template>
  <div class="container--small">
    <ul class="tab">
      <li
        class="tab__item"
        @click="tab = 1"
      >Login</li>
      <li
        class="tab__item"
        @click="tab = 2"
      >Register</li>
    </ul>
    {{ tab }}
  </div>
</template>

<script>
export default {
  data () {
    return {
      tab: 1
    }
  }
}
</script>

タブの機能を実現するために、tab というデータ変数を用意しました。Login タブをクリックすると tab の値が1になり、Register タブをクリックすると tab の値は2になります。

コードが書けたら実際にそれぞれのタブをクリックして {{ tab }} の部分の数字が切り替わることを確認してみましょう。

切り替わるコンテンツ

タブのクリックによってデータ変数 tab の値が切り替えられたので、その値によって表示するコンテンツを追加します。v-show を使えば tab が特定の値のときのみ表示される要素を作成することができます。

{{ tab }} の記述を削除して以下の通りに編集してください。

<ul class="tab">
  <!-- 中略 -->
</ul>
<div class="panel" v-show="tab === 1">Login Form</div>
<div class="panel" v-show="tab === 2">Register Form</div>

<ul> 要素の下にタブによって表示が切り替わるコンテンツを追加します。

ブラウザで「Login Form」と「Register Form」がタブで切り替わることを確認しましょう。

選択状態のスタイルを変える

タブ UI の仕上げに、選択された状態のタブのスタイルを変更します。
それぞれのタブを表す <li> 要素に以下のように :class を追記してください。

<ul class="tab">
  <li
    class="tab__item"
    :class="{'tab__item--active': tab === 1 }"
    @click="tab = 1"
  >Login</li>
  <li
    class="tab__item"
    :class="{'tab__item--active': tab === 2 }"
    @click="tab = 2"
  >Register</li>
</ul>

選択された状態のスタイルは tab__item--active というクラスに定義しています。データ変数 tab の値によってこのクラスの有無を切り替えています。

参考:Vue.js入門 (3) クラスとスタイル

フォームを実装する

次にタブで切り替わるコンテンツの中身であるフォーム UI を実装します。

ログインフォーム

フォーム UI を追加する

「Login Form」と記載していた部分に以下のフォームを追記してください。

<div class="panel" v-show="tab === 1">
  <form class="form">
    <label for="login-email">Email</label>
    <input type="text" class="form__item" id="login-email">
    <label for="login-password">Password</label>
    <input type="password" class="form__item" id="login-password">
    <div class="form__button">
      <button type="submit" class="button button--inverse">login</button>
    </div>
  </form>
</div>

ここまでは何の変哲も無い HTML フォームでしょう。

データ変数の紐づけ

フォームの入力値をスクリプト内部で参照するために v-model を使って入力値とデータ変数を紐づけます。data メソッドの返却値オブジェクトに以下の通り loginForm を追加します。

export default {
  data () {
    return {
      tab: 1,
      loginForm: {
        email: '',
        password: ''
      },
    }
  }
}

続いて <input> 要素に v-model を追加しデータ変数を紐づけます。

<input type="text" class="form__item" id="login-email" v-model="loginForm.email">

<input type="password" class="form__item" id="login-password" v-model="loginForm.password">

送信メソッド

ログインフォーム作成の最後にデータを送信するためのメソッドを追加します。

実際の送信処理は後続の章で実装しますので、今は入力値をメソッドの中で参照できることを確かめるためのログ出力のみを記述しています。

export default {
  data () {/* 中略 */},
  methods: {
    login () {
      console.log(this.loginForm)
    }
  }
}

<form> 要素で、submit イベントのハンドラとして上記の login メソッドを指定します。

<form class="form" @submit.prevent="login">

@submit に続く .prevent はイベント修飾子と呼ばれます。.prevent を記述することは、イベントハンドラで event.preventDefault() を呼び出すのと同じ効果があります。これによりデフォルトのフォーム送信の挙動をキャンセルしページリロードを抑制します。

参考:イベント修飾子


ここまでできたら値を入力して login ボタンをクリックしてみましょう。
コンソールに入力値を含む loginForm オブジェクトが表示されたでしょうか。

会員登録フォーム

会員登録フォームもログインフォームと同じパターンなので、コードはまとめて紹介します。

まずはテンプレート部分です。
「Register Form」と記載していた箇所を以下のフォームに書き換えます。

<div class="panel" v-show="tab === 2">
  <form class="form" @submit.prevent="register">
    <label for="username">Name</label>
    <input type="text" class="form__item" id="username" v-model="registerForm.name">
    <label for="email">Email</label>
    <input type="text" class="form__item" id="email" v-model="registerForm.email">
    <label for="password">Password</label>
    <input type="password" class="form__item" id="password" v-model="registerForm.password">
    <label for="password-confirmation">Password (confirm)</label>
    <input type="password" class="form__item" id="password-confirmation" v-model="registerForm.password_confirmation">
    <div class="form__button">
      <button type="submit" class="button button--inverse">register</button>
    </div>
  </form>
</div>

そしてスクリプト部分です。

export default {
  data () {
    return {
      tab: 1,
      loginForm: {/* 中略 */},
      registerForm: {
        name: '',
        email: '',
        password: '',
        password_confirmation: ''
      }
    }
  },
  methods: {
    login () {/* 中略 */},
    register () {
      console.log(this.registerForm)
    }
  }
}

こちらもコードが書けたら値を入力して register ボタンをクリックしてみましょう。
コンソールに入力値を含む registerForm オブジェクトが表示されたでしょうか。

👾 👾 👾

この章はこれでおしまいです。
本章までのソースコードはリポジトリの chapter-5 ブランチに置いてあります。

次の章では状態管理ライブラリ Vuex を導入し、フロントエンドから Web API を呼び出す処理を実装します。

関連記事

連載記事(全16回)

その他


<!-- Font Awesome Free 5.0.13 by @fontawesome - https://fontawesome.com --><!-- License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) -->