バックエンドにLaravel5.3，フロントにVue.js2.1.1とVuex2.1.1を採用した簡単なWebサイトを作ってみる

# はじめに
2016年もあっという間に終わってしまいましたね．
2017年もどういうライブラリが流行っていくのか楽しみにしております．
2017年しょっぱなからVue.jsとLaravelを使ってサイト構築したので，備忘録的に記録したいと思います．

# この記事でわかること

- Laravelのインストール方法
- Vuexを使った基本的な実装方法

# 参考
Laravel，Vue.js，Vuexと共に日本語ドキュメントが充実しているので，基本的なことはこちらを参考にしました．．

- [Laravel ドキュメント](https://readouble.com/laravel/5.3/ja/)
- [Vue.js ドキュメント](https://jp.vuejs.org/v2/guide/)
- [Vuex ドキュメント](https://vuex.vuejs.org/ja/)

# 作成するアプリの仕様

- API部分
    - `/api/vuex`にリクエストがくると5秒後に`["I'm glad that you waited"]`をレスポンスする．
- WEB部分
    - `/vuex`にアクセスすると`/api/vuex`にリクエストを送信する
    - `/api/vuex`からレスポンスを受け取り，レスポンス結果を表示する．

それでは，Laravelをインストールするところから順に行っていきます．

#実装手順

Laravelのインストール
---------
まずはじめに，Laravelのインストールを行います．

```
$ brew install composer
$ composer global require laravel/installer
$ echo 'export PATH=$HOME/.composer/vendor/bin:$PATH' >> ~/.zshrc #適宜置き換えてください
$ source ~/.zshrc
$ laravel  # 動いたらok
Laravel Installer 1.3.3 
....
```

# 初期プロジェクト作成

```
$ laravel new sample
$ cd sample
$ php artisan serve # localhost:8000にアクセスでWelcomeページが見れるはず
```
package.jsonを見ると標準でVue.jsが同梱されています．
Vuexは，同梱されていないので，Vuexをインストールします．

```
$ npm install
$ npm install vuex --save-dev
```

API部分の作成
-----------
#### ルータの設定
`api/vuex`にアクセスした場合に，レスポンスを返してほしいので，`routes/api.php`に以下を記述します．
また，`/vuex`にアクセスした場合に，リクエストを送信するページに遷移するように設定します．
`routes/api.php`にrouteを設定した場合，自動的に`api`がprefixされます．

```php:routes/api.php
Route::get('/sample', 'SampleAPIController@index');
```

```php:routes/web.php
Route::get('/sample', 'SampleController@index');
```

#### コントローラの作成
コントローラを作成します．

```shell-session:sh
$ php artisan make:controller SampleAPIController
$ php artisan make:controller SampleController
```

```php:app/Http/Controllers/SampleAPIController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class SampleAPIController extends Controller
{
    //
    public function index(){
        return ["I'm glad that you waited"];
    }
}
```

```php:app/Http/Controllers/SampleController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class SampleController extends Controller
{
    //
    public function index(){
        return view('sample');
    }
}
```

#### ビューの作成
Laravelの場合，`make:auth`コマンドを打つと認証系のテンプレートを作成してくれます．今回は認証系の処理は使わないので，認証系の部分のみ削除しそれを利用したいと思います．

```shell-session:sh
$ php artisan make:auth
```

あとは，作成されたファイルを以下のように書き換えたり，作成します．

```html:resouces/views/layouts/app.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <title>{{ config('app.name', 'Laravel') }}</title>

    <!-- Styles -->
    <link href="/css/app.css" rel="stylesheet">

    <!-- Scripts -->
    <script>
        window.Laravel = <?php echo json_encode([
            'csrfToken' => csrf_token(),
        ]); ?>
    </script>
</head>
<body>
    <div id="app">
        @yield('content')
    </div>

    <!-- Scripts -->
    <script src="/js/app.js"></script>
</body>
</html>
```

```resouces/views/sample.blade.php
@extends('layouts.app')

@section('content')
    <div class="container">
        <p>Hello World</p>
    </div>
@endsection
```

WEB部分の作成
-----------

#### VuexのインストールとVue.jsコンポーネントの実装
Laravelのプロジェクトは初めからVue.jsを同梱しています．
また，app.jsを見るとVue.jsがid=appの部分を対象にマウントしていることがわかります(el:の部分にマウントしたい場所の属性を書く)．
Vue.jsのコンポーネントは，マウントした属性のタグ内で適用することができます．

```js:resouces/assets/js/app.js
Vue.component('example', require('./components/Example.vue'));

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

また，上記の`resources/views/layouts/app.blade.php`内にはすでに`app`属性となる部分が存在しています．
つまり，`app.blade.php`を継承したView内では，Vue.jsのコンポーネントを使用できるようになっているということです．
`app.bale.php`を継承したViewを作成します([参考:Laravel 5.3 Bladeテンプレート](https://readouble.com/laravel/5.3/ja/blade.html))．

```html:resources/views/sample.blade.php
@extends('layouts.app')

@section('content')
    <div class="container">
        <p>Hello World</p>
        <example></example>
    </div>
@endsection
```

Vue.jsコンポーネントを使用できるか確認するために，上記のように`<example></example>`を書き加えてみてください．`ExampleComponent`の中身が表示されるはずです．
なお，ExampleComponentの実体は，`resources/assets/js/components/Example.vue`にあります．

Vuexの実装
--------
今回は，1つのカスタムコンポーネント(Vue.js)に対し，1つのModuleファイル(Vuex)を作成します．
手順としては，以下のようになります．

- Moduleの作成
    - stateの作成
    - gettersの作成
    - actionsの作成
    - mutaitonsの作成
- カスタムコンポーネントの作成
- Moduleの作成

#### Moduleの作成
APIにリクエストを送信し，レスポンスを受け取るという動作を挟むためにAjax通信を利用します．
そこで，公式で推奨されているXHRライブラリの`axios`を用います．

```shell-session:sh
$ npm install axios --save-dev
```

まず，axiosを使った非同期処理プログラムを書きます．

```js:resources/assets/js/api/sample.js
import axios from 'axios';

export default{
    sendRequest(url, callback, errorCallBack){
        axios.get(url)
            .then(response => {
                callback(response.data)
            })
            .catch(error => {
                errorCallBack(error);
            })
    },
}
```

次に，componentを書きます．
`mutaiton-types`を作り，ACTION名等の共通化を安易にしています．これによって，IDEからATCTION名等を扱いやすくなります．

```js:resources/assets/js/store/mutation-types.js
export const TOGGLE_STATE = 'TOGGLE_STATE';
export const SEND_REQUEST = 'SEND_REQUEST';
export const CHANGE_CONTENTS = 'CHANGE_CONTENTS';
```

```js:resources/assets/js/store/modules/sample.js
import * as types from '../mutation-types'
import api from '../../api/sample'

const state = {
    receive_response: false,
    response_contents: 'Please wait Response...',
};

// getters
const getters = {
    receiveResponse: state => state.receive_response,
    responseContents: state => state.response_contents,
};

// actions
const actions = {
    [types.TOGGLE_STATE] ({commit}, target){
        commit(types.TOGGLE_STATE,target);
    },
    [types.SEND_REQUEST] ({commit}, url){
        console.log("SEND REQUEST:" + url);
        api.sendRequest(url,
            response => {
                console.log("CATCH RESPONSE:" + url);
                commit(types.CHANGE_CONTENTS, {target:'response_contents', contents:response[0]})
                commit(types.TOGGLE_STATE, 'receive_response')
            },
            error => {
                console.log(error)
            }
        )
    }
};

// mutations
const mutations = {
    [types.TOGGLE_STATE] (state, target){
        state[target] = !state[target];
    },
    [types.CHANGE_CONTENTS] (state, {target, contents}){
        state[target] = contents;
    }
};

export default {
    state,
    getters,
    actions,
    mutations
}
```

#### カスタムコンポーネントの作成
次に実際にカスタムコンポーネントを作成します．
computedが何か，mountedが何か等はドキュメントを見てください．

```html:resources/assets/js/components/Sample.vue
<template>
    <div>
        <div>HelloWorld</div>
        {{ this.responseContents }}
        <div v-if="this.receiveResponse"> Thank you! </div>
    </div>
</template>

<script>
    import {TOGGLE_STATE, SEND_REQUEST} from '../store/mutation-types';
    export default{
        mounted: function() {
            this.$store.dispatch(SEND_REQUEST, '/api/sample')
        },
        computed: {
            receiveResponse(){
                return this.$store.getters.receiveResponse
            },
            responseContents(){
                return this.$store.getters.responseContents
            }
        }
    }
</script>
```

#### Moduleの登録
つぎに，作成したカスタムコンポーネント，Moduleの登録を行います．

```js:resources/assets/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import sample from './modules/sample.js'

Vue.use(Vuex);

export default new Vuex.Store({
    modules: {
        sample
    },
})
```

```js:resources/assets/js/app.js
import store from './store';

Vue.component('example', require('./components/Example.vue'));
Vue.component('sample', require('./components/Sample.vue'));

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

これで，登録完了です．
あとは，`<sample></sample>`と書けば今回の実装は完了です．
ここまでのコードは，[github](https://github.com/kztka/LaravelVuejsVuexSample/tree/v0.1)を見て下さい．

# リファクタリング
`mapState`，`mapAciton`を使うことでコード量を削減することができます．
また，Laravel CollectiveのRoute Annotaitonを使うことでRoutesの記述量を削減することができます．

`mapState`,`mapAction`を適用した例を以下に示します．
CustomComponentの場合，`namespace`を`true`にしないと`mapState`を使えないかもしれません．(詳しく確認してないのですが，私の環境では動きませんでした．)

```html:resources/assets/js/components/Sample.vue
<template>
    <div>
        <div>HelloWorld</div>
        {{ response_contents }}
        <div v-if="receive_response"> Thank you! </div>
    </div>
</template>

<script>
    import {mapState, mapActions} from 'vuex';
    import {TOGGLE_STATE, SEND_REQUEST} from '../store/mutation-types';

    export default{
        mounted: function() {
            this.SEND_REQUEST('/api/sample');
            console.log(this.receive_response);
        },
        computed: {
            ...mapState('sample', [
                'receive_response',
                'response_contents',
            ]),
        },
        methods: {
            ...mapActions('sample', [
                TOGGLE_STATE,
                SEND_REQUEST,
            ])
        }
    }
</script>
```

```js:resources/assets/js/store/modules/sample.js
import * as types from '../mutation-types'
import api from '../../api/sample'

const namespaced = true;

const state = {
    receive_response: false,
    response_contents: 'Please wait Response...',
};

// actions
const actions = {
    [types.TOGGLE_STATE] ({commit}, target){
        commit(types.TOGGLE_STATE,target);
    },
    [types.SEND_REQUEST] ({commit}, url){
        console.log("SEND REQUEST:" + url);
        api.sendRequest(url,
            response => {
                console.log("CATCH RESPONSE:" + url);
                commit(types.CHANGE_CONTENTS, {target:'response_contents', contents:response[0]})
                commit(types.TOGGLE_STATE, 'receive_response')
            },
            error => {
                console.log(error)
            }
        )
    }
};

// mutations
const mutations = {
    [types.TOGGLE_STATE] (state, target){
        state[target] = !state[target];
    },
    [types.CHANGE_CONTENTS] (state, {target, contents}){
        state[target] = contents;
    }
};

export default {
    namespaced,
    state,
    actions,
    mutations
}
```