Laravel
Vue.js
Vuex

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

More than 1 year has passed since last update.


はじめに

2016年もあっという間に終わってしまいましたね.

2017年もどういうライブラリが流行っていくのか楽しみにしております.

2017年しょっぱなからVue.jsとLaravelを使ってサイト構築したので,備忘録的に記録したいと思います.


この記事でわかること


  • Laravelのインストール方法

  • Vuexを使った基本的な実装方法


参考

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


作成するアプリの仕様


  • 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されます.


routes/api.php

Route::get('/sample', 'SampleAPIController@index');



routes/web.php

Route::get('/sample', 'SampleController@index');



コントローラの作成

コントローラを作成します.


sh

$ php artisan make:controller SampleAPIController

$ php artisan make:controller SampleController


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"];
}
}



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コマンドを打つと認証系のテンプレートを作成してくれます.今回は認証系の処理は使わないので,認証系の部分のみ削除しそれを利用したいと思います.


sh

$ php artisan make:auth


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


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のコンポーネントは,マウントした属性のタグ内で適用することができます.


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テンプレート).


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を用います.


sh

$ npm install axios --save-dev


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


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名等を扱いやすくなります.


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';


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が何か等はドキュメントを見てください.


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の登録を行います.


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
},
})



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を見て下さい.


リファクタリング

mapStatemapAcitonを使うことでコード量を削減することができます.

また,Laravel CollectiveのRoute Annotaitonを使うことでRoutesの記述量を削減することができます.

mapState,mapActionを適用した例を以下に示します.

CustomComponentの場合,namespacetrueにしないとmapStateを使えないかもしれません.(詳しく確認してないのですが,私の環境では動きませんでした.)


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>



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
}