vue.js vuex入門 開発で最低限必要そうなこと

はじめに

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>
おわりに

また、作ったものをずらずら書いただけになってしまった。storeではどんな値を管理するのがよいのだろうか。APIから取得した値を一旦すべてStoreに保存するようなことも考えたけどけっこう手間そうな割に実装も複雑になりそう。。アプリの要件次第だけれども極力コンポーネントで完結させるのがシンプルになる気がした。以上です。