9
6

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 5 years have passed since last update.

laravelでVue.jsをつかってみた

Last updated at Posted at 2019-02-13

#はじめに
当初はネイティブJSで実装を行う予定でしたが、laravelがvueをサポートしているということを知り良い機会だと思い取り入れて見ました。

#想定・前提

  • vue理解のため導入箇所を絞ってシンプルに導入(コンポーネントのみ)
  • ルーティングはlaravel側で行う(SPAはなし)
  • バージョンは下記の通り
laravel: 5.7
vue: 2.5.7

#関連ファイルの準備
今回は、予めlaravelのblade側で実装していたテーブルをvueでコンポーネント化し、Twitterでよくみかける無限スクロールっぽい実装を行なっています。

##bladeの設定

  • 共通テンプレートresources/views/layouts/layout.blade.php
layout.blade.php
<!doctype html>
<html lang="ja">
    <head>
        @include('includes.partials.page_meta')
        <link href="{{ mix('/css/common.css') }}" rel="stylesheet" type="text/css">
    </head>
    <body>
        <div id="app" class="container">
            @yield('content')
            @include('includes.footer')
        </div>
        <script src="{{ mix('/js/app.js') }}" defer></script>
    </body>
</html>
  • ページ固有テンプレートresources/views/pages/page.blade.php
page.blade.php
@extends('layouts.layout')

@section('pageMetaTags')

@stop

@section('pageHeadStyles')
<link href="{{ mix('/css/page.css') }}" rel="stylesheet" type="text/css">
@stop

@section('content')

<div class="contents">
    <div class="wrap">
        <demo-component
            results="{{ empty($demo['results']) ? json_encode('') : json_encode($demo['results']) }}"
            :limit="5"
        >
        </demo-component>
    </div>
</div>

@stop

axiosを使わなくても、bladeから直接データを直接渡せるということなのでコントローラー側には手を入れていません。

[results] => Array
    (
        [0] => Array
            (
            [data1] => Array
                (
                    [date] =>
                    [name] => 
                    [item1] => 
                    [item2] => 
                )
            [data2] => Array

コントローラーから受け取るデータが上記のような場合だと、Objectでは渡せないためjson_encodeを使用してJSON形式の文字列にしてコンポーネント側に渡し配列に変換が必要でした。

##webpackの設定

webpack.mix.js
let mix = require('laravel-mix');

mix.disableNotifications()
    .options({
        postCss: [
            require('autoprefixer')({
                grid: true,
                browsers: ['ie >= 9', 'Edge >= 12', 'ff >= 50', 'chrome >= 56', 'Safari >= 7', 'ios_saf >= 8', 'Android >= 4']
            })
        ]
    })
    //.js('resources/assets/js/common.js', 'public/js')
    .js('resources/assets/js/app.js', 'public/js');

if (!mix.inProduction()) {
    mix.webpackConfig({
        devtool: 'source-map'
    });
    mix.sourceMaps();
}
if (mix.inProduction()) {
    mix.version();
}

もともと共通用のスクリプトを書いていたcommon.jsはvueを使用するにあたりapp.jsにimportさせています。

##インスタンスの設定

app.js
window.Vue = require('vue');

Vue.component('demo-component', require('./components/demoComponent.vue'));

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

##コンポーネントファイルの設定

demoComponent.vue

<template>
    <div v-bind:class="{'table-scroll': wrapFlg}">
        <table>
            <thead>
            <tr>
                <th>Date</th>
                <th>Name</th>
                <th>Item1</th>
                <th>Item2</th>
            </tr>
            </thead>
            <tbody v-if="records.length > 0">
                <tr v-for="record in records">
                    <td>{{ record.date }}</td>
                    <td>
                        <a :href="'/detail/' + record.id">{{ record.name }}</a>
                    </td>
                    <td>{{ record.item1 }}</td>
                    <td>{{ record.item2 }}</td>
                </tr>
                <tr v-show="loader">
                    <td colspan="4">
                        <i class="icon-loader"></i>
                    </td>
                </tr>
            </tbody>
            <tbody v-else>
                <tr>
                    <td colspan="4">
                        <span>データはまだありません。</span>
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
</template>

<style scoped>
    .table-scroll {
        overflow-x: scroll;
    }
    .icon-loader {
        display: inline-block;
        background-image: url(/img/loader.gif);
        background-repeat: no-repeat;
        background-size: contain;
        width: 30px;
        height: 30px;
        vertical-align: middle;
    }
</style>

<script>
    export default {
        props: {
            results: {
                type: String,
                default: ''
            },
            wrapFlg: {
                type: Boolean,
                default: true,
            },
            limit: {
                type: Number,
                default: 10
            }
        },
        data () {
            return {
                timer: null,
                startPos: 0,
                records: [],
                loader: false
            }
        },
        created: function () {
            for (let cnt = 0; cnt < this.limit; cnt++) {
                if (this.parseResults[cnt]) {
                    this.records.push(this.parseResults[cnt]);
                }
            }
        },
        mounted: function () {
            window.addEventListener('scroll', this.infiniteScroll);
        },
        beforeDestroy: function () {
            window.removeEventListener('scroll', this.infiniteScroll);
        },
        computed: {
            parseResults: function () {
                return JSON.parse(this.results);
            }
        },
        methods: {
            async fetch () {
                if (this.parseResults[this.records.length]) {
                    this.loader = true;
                    if (this.timer === null) {
                        this.timer = setTimeout(function () {
                            this.records.push(this.parseResults[this.records.length]);
                            this.timer = null;
                            this.loader = false;
                        }.bind(this), 500);
                    }
                }
            },
            getElementRect: function () {
                return this.$el.getBoundingClientRect();
            },
            infiniteScroll: function () {
                const {top, height} = this.getElementRect();
                const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
                const targetTop = top + scrollTop;
                if (window.innerHeight + scrollTop > targetTop + height) {
                    if (scrollTop > this.startPos) {
                        this.fetch();
                    }
                }
                this.startPos = scrollTop;
            }
        }
    }
</script>

#つかってみて
はじめてvueを使ってみましたが、思いのほか全体の設計を変えずに導入できました。
templateもv-bindなどvue独自のものを除けば、比較的bladeの記述に近い形になるのと日本語マニュアルにある程度網羅されているので、普段laravelに触れている方は気軽に始めることができるのかと思いました。

また、自由度が高くフレームワーク依存が少ないので後からの切り離しも楽かと。

9
6
0

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
9
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?