Laravelの基本はこちらから学べます

LaravelでVue.jsを利用する(いいね機能の拡張 with Axios)




初めてのコンポーネント

今回は、以前同期処理で実装した「いいね機能」をVue.jsに書き換えて実装してみたいと思います。
まずはLaravelをインストールした際に元々入っているExampleComponentを活用してみましょう。

// app/resources/js/components/ExampleComponent.vue

<template>
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-12">
                <div class="card">
                    <div class="card-header">Example Component</div>

                    <div class="card-body">
                        I'm an example component.
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        mounted() {
            console.log('Component mounted.')
        }
    }
</script>

このようにLaravelには初めての人でも使いやすいように、例としてコンポーネントが初期搭載されています。
次にapp.jsを確認しましょう。

app.jsには既にVueコンポーネントが登録されていますね。

// app/resources/js/app.js

/**
 * First we will load all of this project's JavaScript dependencies which
 * includes Vue and other libraries. It is a great starting point when
 * building robust, powerful web applications using Vue and Laravel.
 */

require('./bootstrap');

window.Vue = require('vue');

/**
 * The following block of code may be used to automatically register your
 * Vue components. It will recursively scan this directory for the Vue
 * components and automatically register them with their "basename".
 *
 * Eg. ./components/ExampleComponent.vue -> <example-component></example-component>
 */

// const files = require.context('./', true, /\.vue$/i)
// files.keys().map(key => Vue.component(key.split('/').pop().split('.')[0], files(key).default))

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

/**
 * Next, we will create a fresh Vue application instance and attach it to
 * the page. Then, you may begin adding components to this application
 * or customize the JavaScript scaffolding to fit your unique needs.
 */

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

次にwebpack.mix.jsを確認しましょう。

webpackとは、モジュールバンドラのことを指します。。。モジュールバンドラ?
モジュールバンドラとは複数のファイルを1つにまとめて出力してくれるツールのことをいいます。
なるほど、、なかなか難しいですね。

昨今の大規模なwebアプリケーションには様々なJavaScriptファイルが多数存在します。機能ごとに、ファイルを分けた方が保守性が良いですが、たくさんのファイルを読み込もうとするとその度にリクエストが発生して重くなってしましますよね。

そこで、管理する際には複数ファイルで管理し、読み込む際には複数のファイルを1つにまとめてあげたらいいのでは!!これがwebpackです。

Laravel Mixは多くの一般的なCSSとJavaScriptのプリプロセッサを使用し、Laravelアプリケーションために、構築過程をWebpackでスラスラと定義できるAPIを提供しています

公式ドキュメント https://readouble.com/laravel/6.x/ja/mix.html
// webpack.mix.js

const mix = require('laravel-mix');

/*
 |--------------------------------------------------------------------------
 | Mix Asset Management
 |--------------------------------------------------------------------------
 |
 | Mix provides a clean, fluent API for defining some Webpack build steps
 | for your Laravel application. By default, we are compiling the Sass
 | file for the application as well as bundling up all the JS files.
 |
 */

mix.js('resources/js/app.js', 'public/js')
   .sass('resources/sass/app.scss', 'public/css');

上記のように記入がされていると思いますので、そのままでOKです!

実際に、一覧ページにコンポーネントを出力してみましょう!

//posts/index.balade.php

@extends('layouts.app')

@section('content')
<div class="container">
    <example-component></example-component>

//省略
//bash
% npm run watch

npm run watch コマンドはターミナルで実行し続け、関連ファイル全部の変更を監視します。webpackは変更を感知すると、アセットを自動的に再コンパイルします。

公式ドキュメント https://readouble.com/laravel/6.x/ja/mix.html

今後、Vueコンポーネントを書き換えた際にはコンパイル が必要になりますので、上記コマンドで変更を監視して置くことをお勧めします!
変更したら毎回、npm run dev コマンドを打ってもOKです。やりやすい方でやりましょう!

初めてのVueコンポーネントの導入おめでとうございます!

いいね機能の実装

では次に「いいね」機能をVue.jsで書き変えていきます

// app.js

Vue.component('like-component', require('./components/LikeComponent.vue').default);
// LikeComponent.vue

<template>
    <div class="container">
        <div class="row justify-content-center mt-1">
            <div class="col-md-12">
                <div>
                    <button>
                        いいね解除
                    </button>
                    <button>
                        いいね
                    </button>
                    <p>いいね数:</p>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        mounted() {
            console.log('Like Component mounted.')
        }
    }
</script>

今回は「LikeComponent.vue」を作成し、そこで「いいね」「いいね解除」ボタンをコンポーネント化します。
コンポーネント化は噛み砕いて言うと、再利用(何度でも利用)できるようにすることです。

// index.blade.php

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card text-center">
                <div class="card-header">
                    投稿一覧
                </div>
                @foreach ($posts as $post)
                <div class="card-body">
                    <h5 class="card-title">タイトル:{{ $post->title }}</h5>
                    <p class="card-text">内容:{{ $post->body }}</p>
                    <p class="card-text">投稿者:{{ $post->user->name }}</p>
                    <a href="{{ route('posts.show', $post->id) }}" class="btn btn-primary">詳細へ</a>
                    <div class="row justify-content-center">
                        <like-component
                            :post="{{ json_encode($post)}}"
                        ></like-component>
                    </div>
                </div>
                <div class="card-footer text-muted">
                    投稿日時:{{ $post->created_at }}
                </div>
                @endforeach
            </div>
        </div>
        <div class="col-md-2">
            <a href="{{ route('posts.create') }}" class="btn btn-primary">新規投稿</a>
        </div>
    </div>
</div>
@endsection

新しく、json_encodeがでてきました。

値を JSON 形式にして返す

PHPマニュアル https://www.php.net/manual/ja/function.json-encode.php

まず、JSONってなんや、13日の金曜日にでてくる……と思ってしまった人はググってみましょう。

JavaScript Object Notation (JSON) は表現用の標準的なテキストベースの構造データ表現フォーマットで、JavaScript 構造データオブジェクトの表記法をベースとしています。一般的にはウェブアプリケーションでデータを転送する場合に使われます。

MDN web docs https://developer.mozilla.org/ja/docs/Learn/JavaScript/Objects/JSON

Vue.jsのコンポーネントにPHPからデータを渡す際には、JSON形式にして渡してあげましょう!

// LikeComponent.vue

<script>
    export default {
        props: ['post'],
        mounted () {
            console.log(this.post);
        }
     }
</script>

JSON形式でPHPから送られてきたデータはpropsで受け取ります。
上記では、受け取ったデータはpostとして受け取っていますね。

console.logで受け取ったpostに何が入っているかを確認しましょう!

検証ツール→Consoleで確認します。

素晴らしい!ちゃんと送りたいデータがコンポーネント側で受け取れていますね。

Axiosを使ってみる

それでは次に実際に「いいね」「いいね解除」を作成していきましょう。

// web.php おそらくすでに記入いただいてます。

Route::get('posts/{post}/favorites', 'FavoriteController@store');
Route::get('posts/{post}/unfavorites', 'FavoriteController@destroy');

web.phpで以前の「いいね」「いいね解除」に利用していたルーティングを使います。

// LikeComponent.vue

<template>
    <div class="container">
        <div class="row justify-content-center mt-1">
            <div class="col-md-12">
                <div>
                    <button @click="unfavorite()">
                        いいね解除
                    </button>
                    <button @click="favorite()">
                        いいね
                    </button>
                    <p>いいね数:</p>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        props: ['post'],
        mounted () {
            console.log(this.post);
        },
        methods: {
            favorite() {
                axios.get('/posts/' + this.post.id +'/favorites')
                .catch(function(error) {
                    console.log(error);
                });
            },
            unfavorite() {
                axios.get('/posts/' + this.post.id +'/unfavorites')
                .catch(function(error){
                    console.log(error);
                });
            },
        }

     }
</script>

axiosはLaravelでも採用されている、Ajax通信を行えるJavaScriptライブラリです。
Ajax通信は非同期(サーバーと通信中であっても別の処理を引き続き行えること)通信を行うための技術です。

一部の情報をサーバーに送信して、それを受け取り反映させる仕組み

Ajaxとは、あるWebページを表示した状態のまま、別のページや再読込などを伴わずにWebサーバ側と通信を行い、動的に表示内容を変更する手法。ページ上でプログラムを実行できるプログラミング言語JavaScript拡張機能を用いる。

IT用語辞典 http://e-words.jp/w/Ajax.html
// FavoriteController.php

// 省略

    public function store($id)
    {
        $post = Post::find($id);
        $post->users()->attach(Auth::id());
    }

// 省略

    public function destroy($id)
    {
        $post = Post::find($id);
        $post->users()->detach(Auth::id());
    }

流れを確認すると、
①いいねボタンを押すと、favorite関数が作動
②axiosでURlにGETで通信
③web.phpでルーティング
④FavoriteController@storeが動く
⑤いいねがされたことがデータベースに保存される

「いいね解除」についても同じような動きになりますね。

いいね数を表示する

次に、その投稿に対していいねが何件ついているのかも取得し、表示してみましょう。

// FavoriteController.php

    public function store($id)
    {
        $post = Post::find($id);
        $post->users()->attach(Auth::id());
        $count = $post->users()->count(); //以下追加
        return response()->json([
            'count' => $count, 
        ]);
    }

    public function destroy($id)
    {
        $post = Post::find($id);
        $post->users()->detach(Auth::id());
        $count = $post->users()->count(); //以下追加
        return response()->json([
            'count' => $count, 
        ]);
    }

   public function count ($id) //以下追加
    {
        $post = Post::find($id);
        $count = $post->users()->count();

        return response()->json($count);
    }

storeメソッド、destroyメソッドの変更点

まず、storeメソッド及びdestroyメソッドで、投稿に何件いいねが保存されているかcount()で取得しています。
取得した、いいね数をJSON形式で返り値として渡してあげましょう。

countメソッド

こちらは単純に、受け取った$idで特定の投稿を取得。その投稿に対するいいね数をcount()で取得しています。
こちらも、取得したいいね数をJSON形式で返り値として渡してあげましょう。

// LikeComponent.vue

<template>
    <div class="container">
        <div class="row justify-content-center mt-1">
            <div class="col-md-12">
                <div>
                    <button @click="unfavorite()" class="btn btn-danger">
                        いいね解除
                    </button>
                    <button @click="favorite()" class="btn btn-success">
                        いいね
                    </button>
                    <p>いいね数:{{ count }}</p>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        props: ['post'],
        data() {
            return {
                count: ""
            }
        },
        mounted () {
            this.countfavorites();
        },
        methods: {
            favorite() {
                axios.get('/posts/' + this.post.id +'/favorites')
                .then(res => {
                    this.count = res.data.count;
                }).catch(function(error) {
                    console.log(error);
                });
            },
            unfavorite() {
                axios.get('/posts/' + this.post.id +'/unfavorites')
                .then(res => {
                    this.count = res.data.count;
                }).catch(function(error){
                    console.log(error);
                });
            },
            countfavorites() {
                axios.get('/posts/' + this.post.id +'/countfavorites')
                .then(res => {
                    this.count = res.data;
                }).catch(function(error){
                    console.log(error);
                });
            },
        }
    }
</script>

いいね数の表示部分の変更

<template>内に、<p>いいね数:{{ count }}</p>を追加。countが表示されるようにしましょう。

dataオプションの登録

まず、dataオプションにcountを登録しましょう。こちらに、いいね 数を格納していきます。

storeメソッド、destroyメソッドの修正

axios.get('/posts/' + this.post.id +'/favorites')
.then(res => {
   this.count = res.data.count;
}).catch(function(error) {
   console.log(error);
});

axiosを用いてGET通信を行います、thenを使用することで通信が成功した際の処理を書くことができます。
一方、catchを使用してエラー時の処理を記入しています。

通信が成功した際(then)の処理をもう少し細かくみてみましょう。
resはレスポンスのことで、FavoriteController.phpでJSON形式にしたいいね数を取得できています。
そのいいね 数(this.data.count)を先ほど追加したdataプロパティのcountに格納しています。

countfavoriteメソッドの作成

countfavorites() {
                axios.get('/posts/' + this.post.id +'/countfavorites')
                .then(res => {
                    this.count = res.data;
                }).catch(function(error){
                    console.log(error);
                });
            },

こちらはいいね 数をカウントするためだけのメソッドになります。
ページに遷移してきた際にいいね数を表示するために、mountedに登録しておきましょう。

// web.php

Route::get('posts/{post}/countfavorites', 'FavoriteController@count');

web.phpに追加

FovoriteControllerのcountメソッドを発火させるためのルーティングを追記しましょう。

ここまでできたら、いいね数が表示されるかを確認してみましょう!

//bash
% npm run watch

コンポーネントの変更は監視していないと表示されないと思うので、ここらでターミナルを一度確認してみましょう。

いいねボタンの表示、非表示

現状、「いいねボタン」「いいね解除ボタン」共にずっと表示され続けています。
すでに「いいね」しているユーザーには「いいね解除」
「いいね」していないユーザーには「いいね」ボタンを表示できるようにしましょう。

// web.php

Route::get('posts/{post}/hasfavorites', 'FavoriteController@hasfavorite');
// FavoriteController.php

    public function store($id)
    {
        $post = Post::find($id);
        $post->users()->attach(Auth::id());
        $count = $post->users()->count(); 
        $result = true; //追加
        return response()->json([
            'result' => $result, 
            'count' => $count,  
        ]);
    }

    public function destroy($id)
    {
        $post = Post::find($id);
        $post->users()->detach(Auth::id());
        $count = $post->users()->count();
        $result = false; //追加
        return response()->json([
            'result' => $result, 
            'count' => $count, 
        ]);

    
  public function hasfavorite ($id)
    {
        $post = Post::find($id);

        if ($post->users()->where('user_id', Auth::id())->exists()) {
            $result = true;
        } else {
            $result = false;
        }

        return response()->json($result);
    }

storeメソッド、destroyメソッドの変更点

storeメソッドでは$resultにtrueを代入(いいね済みのフラグ)。反対に、destroyメソッドではfalseを代入しJSONで返しています。

hasfavotiteメソッドの追加

こちらのメソッドでは、ログイン中のユーザーがいいねをしているかどうかを判断するものになります。ページにアクセスしてきた際に、いいねボタンorいいね取り消しボタンを出すかを判定するためのデータをJSONで返却します。

// LikeComponent.vue

//省略

<button @click="unfavorite()" class="btn btn-danger" v-if="result">
    いいね解除
</button>
<button @click="favorite()" class="btn btn-success" v-else>
    いいね
</button>

//省略

<script>
    export default {
        props: ['post'],
        data() {
            return {
                count: "",
                result: "false" //追加
            }
        },
        mounted () {
            this.hasfavorites();
            this.countfavorites(); //追加
        },
        methods: {
            favorite() {
                axios.get('/posts/' + this.post.id +'/favorites')
                .then(res => {
                    this.result = res.data.result;
                    this.count = res.data.count;
                }).catch(function(error) {
                    console.log(error);
                });
            },
            unfavorite() {
                axios.get('/posts/' + this.post.id +'/unfavorites')
                .then(res => {
                    this.result = res.data.result;
                    this.count = res.data.count;
                }).catch(function(error){
                    console.log(error);
                });
            },
            countfavorites() {
                axios.get('/posts/' + this.post.id +'/countfavorites')
                .then(res => {
                    this.count = res.data;
                }).catch(function(error){
                    console.log(error);
                });
            },
            hasfavorites() { //追加
                axios.get('/posts/' + this.post.id +'/hasfavorites')
                .then(res => {
                    this.result = res.data;
                }).catch(function(error){
                    console.log(error);
                });
            }
        }
    }
</script>

template内の変更

いいね解除ボタン、いいねボタンに v-if、v-else で条件分岐を追記しました。
resultがtrueであれば、いいね解除ボタンが表示され、falseであればいいねボタンが表示できるようになりました。

script内の変更

dataオプションにresultを新規で追加しました。こちらには、すでにユーザーがいいねを押していたらtrue、押していなければfalseを格納します。

hasfavoritesメソッドを追加しました。こちらのレスポンスをresultに格納しています。

ここまで記入することができれば、実装は完了です!
いかがだったでしょうか。非同期通信を使うことで、ユーザー体験が向上したのではないでしょうか!

Vue.jsのオススメ書籍

私が何か分からなくなった際に読んでいるのは下記の猫が特徴的な書籍です!
Kindle版で購入しましたが、前から全てをこなしていくのではなく、実装で詰まった時に辞書がわりにして使っています!


お疲れ様でした!




コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です