hkのweblog

ひよっこエンジニアがにわとりになるまでの軌跡

Laravel 5.5 + Vue 2.1でSPA的なものを作っている話のつづき

 少し間が空きましたが、前回の記事の続きです。
前回は複数のモデルのリレーションを設定して、jsonを返してやるところまで進めました。これですね↓

{
    sento_code: 1,
    sento_name: "テルマー湯",
    address: "東京都新宿区歌舞伎町1-1-2",
    tel: "03-5285-1726",
    sento_map: {
        sento_code: 1,
        lat: 35.694535,
        lng: 139.705160
    }
}

今回は返ってきたjsonを展開してみます。

Laravel側で最初に読み込むviewを設定する

 Single Page Applicationは文字通り最初のリクエスト時に1つのページを返しますが、以降はhtmlを返さず同じページを使いまわします。
APIにリクエストを投げて、返ってきたjsonを基にページを書き換えていくようなイメージになります。
今回の場合、最初のリクエスト時にベースとなるページを返したり、モデル周りなどサーバーサイドの処理を司るのがLaravel、以降ページを書き換えたりルーティングをしたりするのはVue側の役割になります。

というわけでまずはweb.phpに最初に読み込むページを設定します。

<?php

Route::get('/{any}', function () {
    return view('app');
})->where('any', '.*');

ざっくり言うと、「スラーの後に何が来ようとapp.blade.phpを返すよ」ということを書いています。

次に読み込まれるapp.blade.phpが以下です。

<!DOCTYPE html>
<html lang="{{config('app.locale')}}">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X=UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="csrf-token" content="{{ csrf_token() }}">
        <title>銭湯まっぷ</title>
        <link rel="stylesheet" href="{{mix('css/app.css')}}">
        <script>
            window.Laravel = {
                csrfToken: "{{csrf_token()}}"
            };
        </script>
    </head>
    <body>
        <div id="app">
            <navbar></navbar>
            <div class="container">
                <router-view></router-view>
            </div>
        </div>
    </body>
    <script src="{{mix('js/app.js')}}"></script>
</html>

静的ファイルの読み込み方とかcsrfがどうだとか細かく説明すべき箇所はたくさんあるのですが、今回は端折ります。
重要な役割を担うのは最後のほうで読み込んでいるapp.jsです。こいつが<div id="app">の中を書き換えます。

ルーティングの設定

import Vue from 'vue';
import VueRouter from 'vue-router';

window.Vue = require('vue');

require('./bootstrap');

Vue.use(VueRouter);

Vue.component('navbar', require('./components/Layouts/Navbar.vue'));

const router = new VueRouter({
    mode: 'history',
    routes: [
        {path: '/', component: require('./components/Index.vue')},
        {path: '/about', component: require('./components/About.vue')},
        {path: '/detail/:sento_code', component: require('./components/Detail.vue')},
    ]
});

const app = new Vue({
    router
}).$mount('#app');

私自身、SPAについて何も知らなかった頃は「SPAってURL固定でどうやってAPIリクエストするんだろう…」と思っていましたが、当然ルーティングはあります。
vue-routerというのが必要になるので予めpackage.jsonに書いてnpm installしておきましょう。
今回は'/'を叩くとIndex.vueを読み込み、'/about'を叩くとAbout.vueを読み込む…という構造にしています。
3つめだけ少し勝手が違いますが、'/detail/1'を叩くとsento_code=1というパラメータを渡しつつDetail.vueを読み込みます。 この他にコンポーネントを作っている部分があります。こんな書き方で<navbar></navbar>のところにNavbar.vueを読み込むことができます。
説明の順番がおかしい感じもしますが、最後の3行でVueインスタンスを生成しています。これで<div id="app">の中を書き換えることができます。

Vueのテンプレートの中でjsonを読み込む

さて、ようやく本題。Vueのテンプレートの中でjsonを読み込みます。
いくつかvueを読み込んでいますが、今回はこの中でDetail.vueについて書きます。
先程のjsonを読み込んで銭湯の情報の詳細ページを作るイメージです。
シンプルな構造のjsonのサンプルなら結構転がっているのですが、ちょっと階層が深くなると全然出てこなくて苦労しました…

<template>
    <div>
        <table>
            <tr>
                <th>施設名</th>
                <td>{{sento.sento_name}}</td>
            </tr>
            <tr>
                <th>住所</th>
                <td>{{sento.address}}</td>
            </tr>
            <tr>
                <th>電話番号</th>
                <td>{{sento.tel}}</td>
            </tr>
            <tr>
                <th>緯度</th>
                <td>{{sento.sento_map.lat}}</td>
            </tr>
            <tr>
                <th>経度</th>
                <td>{{sento.sento_map.lng}}</td>
            </tr>   
        </table>
    </div>
</template>

<script>
export default {
    created() {
        this.getSento()
    },
    data() {
        return {
            sento: {
                sento_name: '',
                address: '',
                tel: '',
                sento_map: {
                    lat: '',
                    lng: '',
                }
            }
        }
    },
    methods: {
        getSento() {
            this.$http.get('/api/sento/' + this.$route.params.sento_code)
            .then(res => {
                this.sento = res.data
            })
        }
    }
}
</script>

まず、<script>以下から見ていきます。
このへんは疎いのですが、export defaultはimportする際に特に指定がなければそのクラスや関数を呼ぶということのようです。
今回はgetSentoを呼んでいます。
下の方にgetSentoを書いています。'/api/sento/'のうしろにthis.$route.params.sento_codeとありますが、これが先ほどvue-routerで書いたパラメータです。
jQueryなんかと似ていますが、帰ってくるres.dataをsentoに渡してやって、これをテンプレートの中で展開していきます。
dataの部分では帰ってくるオブジェクトの型を書いています。主に苦労したのはここの構造だったりします。
cssを全く書いていないので表示はぐちゃぐちゃですが、表示されてほしいものはこれで表示されるはずです。
Vue.jsはまだまだ勉強中ですが、シンプルかつ便利な機能が豊富に準備されていて、おしゃれなアニメーションとかも作りやすそうです。
全体的に駆け足になりましたが、今日はこんなところで。