はじめに
vue.js使うことになりそうなので少しいじってみたのでメモしておく。
とりあえず今回やってみようと思ったことは以下
・ヘッダー、フッター、メニューなどの共通化
・vue-routeを使ったルーティング
・ログイン機能
・APIへのリクエスト
・vuexの導入
セットアップ
プロジェクトを作成してvuexとaxiosをインストール。vueでのajaxしたい場合axiosが奨励されてるぽい?
$ vue init webpack test $ npm install --save vuex axios
作る画面
・ログイン画面
・商品一覧画面
・商品詳細画面
こんなかんじの管理画面とかでありそうな画面を想定。ログイン画面以外に未ログイン状態でアクセスしたらログイン画面へリダイレクト。商品一覧と詳細画面ではAPIへリクエストして取得したjsonを画面へ表示する。
ディレクトリ構成
vuexのexampleとか他の人のソースみてるとこんな感じなのかな。
$ tree src
├── App.vue ## この辺はおきまりぽい
├── main.js
├── api ## APIリクエスト
│ └── index.js
├── components ## コンポーネント
│ ├── Element
│ │ ├── Footer.vue
│ │ └── Header.vue
│ ├── Product
│ │ ├── Detail.vue
│ │ └── List.vue
│ ├── Login.vue
│ ├── Member.vue
│ └── Root.vue
├── router ## ルーティング
│ └── index.js
└── store ## vuex関連
├── index.js
└── modules
└── auth.jsまた、config以下のdev.env.jsとprod.env.jsに環境ごとに変わる定数などを定義するのがよさそう。今回はAPIのURLをここに書いた。
var merge = require('webpack-merge')
var prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
NODE_ENV: '"development"',
API_URL: '"http://api.example.com/"'
})
実装
main.js
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store' // ★ 追加
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store, // ★ 追加
template: '<App/>',
components: { App }
})基本的に今回はvuexを使うのでstoreをVueのオプションに追加。
ルーティング
router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import Auth from '@/store/modules/auth'
import Root from '@/components/Root'
import Login from '@/components/Login'
import Member from '@/components/Member'
import ProductList from '@/components/Product/List'
import ProductDetail from '@/components/Product/Detail'
Vue.use(Router)
var router = new Router({
routes: [
{
path: '/',
component: Root,
redirect: '/member/list',
// 大きく「login」と「member」に分ける。member以下はログインしてないと遷移できないように。
children: [
{
path: 'login',
name: 'login',
component: Login
},
{
path: 'member',
name: 'member',
component: Member,
meta: {
requiresAuth: true
},
// 各ページは以下に追加していく
children: [
{
path: 'product/list',
name: 'product/list',
component: ProductList
},
{
path: 'product/detail/:id',
name: 'product/detail',
component: ProductDetail
}
]
}
]
}
]
})
// ログイン認証はここで行う
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth) && !Auth.state.loggedIn) {
next({
path: '/login'
})
} else {
next()
}
})
export default router
APIリクエスト
きっとアプリごとに共通のhttpヘッダーとか送ることが多いと思うのでそういうのはここにまとめるようにする。
api/index.js
import axios from 'axios'
export default {
request (method, url, params) {
var promise = null
url = process.env.API_URL + url
if (method === 'get') {
promise = axios.get(url, { params: params })
} else if (method === 'post') {
promise = axios.post(url, params)
}
promise.catch(function () {
return alert('エラーが発生しました')
})
return promise
},
get (url, params) {
return this.request('get', url, params)
},
post (url, params) {
return this.request('post', url, params)
}
}
vuex関連
今回はログイン関連の情報をstoreで管理する。
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import auth from './modules/auth'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
auth
}
})store/modules/auth.js
import api from '@/api'
export default {
namespaced: true,
state: {
loggedIn: false
},
mutations: {
login (state) {
state.loggedIn = true
},
logout (state) {
state.loggedIn = false
}
},
actions: {
login ({ commit }, payload) {
api.get('/login', {
mail: payload.mail,
pass: payload.pass
}).then(function (response) {
commit('login')
// ログイン後、リダイレクト
payload.router.push('/member')
})
}
}
}
コンポーネント
残りはコンポーネントを実装して画面を作っていく。
| コンポーネント | 概要 |
|---|---|
| Root.vue | 全コンポーネントのルートとなる |
| Login.vue | 未ログインの画面 |
| Member.vue | ログイン済の画面のルートとなる。ヘッダやフッターなどはここで表示する |
| Element/Header.vue(Footer.vue) | ヘッダー(フッター) |
| Product/List.vue | 商品一覧画面(Memberの中に埋め込まれる) |
| Product/Detail.vue | 商品一覧画面(Memberの中に埋め込まれる) |
components/Root.vue
<template> <router-view></router-view> </template>
components/Member.vue
<template>
<div>
<app-header></app-header>
<router-view></router-view>
<app-footer></app-footer>
</div>
</template>
<script>
import Header from '@/components/Element/Header.vue'
import Footer from '@/components/Element/Footer.vue'
export default {
name: 'root',
components: {
'app-header': Header,
'app-footer': Footer
}
}
</script>components/Login.vue
<template>
<div>
<h1>ログインページ</h1>
id:<input type="text" v-model="id" /><br />
password:<input type="password" v-model="password" /><br />
<input type="button" value="LOGIN" @click="onLogin" />
</div>
</template>
<script>
import { mapActions } from 'vuex'
export default {
data: function () {
return {
id: null,
password: null
}
},
methods: {
...mapActions('auth', [
'login'
]),
onLogin: function () {
var self = this
self.login({
mail: this.$data.id,
pass: this.$data.password,
router: self.$router
})
}
}
}
</script>components/Product/List.vue
<template>
<div>
<h1>一覧ページ</h1>
<table>
<tr>
<th>id</th>
<th>name</th>
</tr>
<tr v-for="item in items">
<td>{{ item.id }}</td>
<td><router-link :to="{ name: 'product/detail', params: {id: item.id} }">{{ item.name }}</router-link></td>
</tr>
</table>
</div>
</template>
<script>
import api from '@/api'
export default {
data: function () {
return {
items: []
}
},
beforeCreate: function () {
var self = this
api.get('/product')
.then(function (response) {
self.items = response.data.items
})
}
}
</script>components/Product/Detail.vue
<template>
<div>
<h1>詳細ページ</h1>
<a v-on:click="$router.go(-1)">戻る</a>
<table>
<tr>
<th>id</th><td>{{ item.id }}</td>
</tr>
<tr>
<th>name</th><td>{{ item.name }}</td>
</tr>
</table>
</div>
</template>
<script>
import api from '@/api'
export default {
data: function () {
return {
item: {}
}
},
beforeCreate: function () {
var self = this
api.get('/product/detail', {
id: self.$route.params.id
})
.then(function (response) {
self.item = response.data.item
})
}
}
</script>