Murayama blog.

プログラミング教育なブログ

30minくらいで学ぶVue.jsとVuex

カウンターアプリケーションの開発を通じてVue.jsによるプログラミングとVuexによる状態管理を学びます。

f:id:yamasahi:20190213141451p:plain

ボタンを押したら数字が増えていくアプリケーションです。

Agenda

  • Part 1 Vueアプリケーションの開発(10min)
  • Part 2 Vuexを活用したVueアプリケーションの開発(10min)
  • Part 3 非同期処理(Actions)の実装(10min)

Part 1 Vueアプリケーションの開発(10min)

プロジェクトの作成

プロジェクトの雛形を作成するためにvue-cliをインストールします。

npm install -g @vue/cli

インストールしたvue-cliを使ってプロジェクトの雛形を作成します。

vue create counter-app

vue-cliの設定はデフォルトを使います。途中でVuexを追加しますが手動で追加するものとします。

プロジェクトが作成できたらディレクトリを移動してサーバを起動してみましょう。ここではyarnを使って起動しています(インストールの設定によってはnpmでも大丈夫です)。

cd counter-app
yarn serve

次のような画面を確認できればOKです。

f:id:yamasahi:20190213141744p:plain

Vueコンポーネント(MyCounter.vue)の作成

vue-cliで作成したプロジェクトの開発では主にsrcディレクトリ下のファイルを編集することになります。

  • src/
    • assets/
      • 画像ファイルなどのアセット
    • components/
      • App.vueからロードされる画面の部品となるVueファイル
    • App.vue
      • main.jsからロードされるVueファイル
    • main.js
      • 起動ファイル

src/components/MyCounter.vueファイルを新規作成します。

<template>
    <div>
        Count: {{ count }}
        <button @click="onclick">Increment</button>
    </div>
</template>

<script>
export default {
    data () {
        return {
            count: 1
        }
    },
    methods: {
        onclick () {
            this.count++
        }
    }
}
</script>

続いてsrc/App.vueを修正します。

<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
-    <HelloWorld msg="Welcome to Your Vue.js App"/>
+    <MyCounter />
  </div>
</template>

<script>
-import HelloWorld from './components/HelloWorld.vue'
+import MyCounter from '@/components/MyCounter.vue'

export default {
  name: 'app',
  components: {
-    HelloWorld
+    MyCounter
  }
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

ブラウザでサーバ(localhost:8080)にアクセスすると次のような画面が表示されるでしょう。

Part 2 Vuexを活用したVueアプリケーションの開発(10min)

Vuexのインストール

つづいてVuexをインストールします。

VuexはVue.jsアプリケーションのための状態管理パターン+ライブラリです。 https://vuex.vuejs.org/ja/

$ yarn add vuex --save

ここではMyCounter.vueの状態(dataプロパティ)をVuex上で管理するように修正していきます。

Storeの作成

Vuex上で状態を管理するためのStoreファイルを作成します。src/store/index.jsを作成し、以下のように実装します。

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
    state: {
        count: 0
    },
    mutations: {
        increment (state) {
            state.count++
        },
    },
})

export default store

VuexのStoreには以下の主に4つを記述します。

  • state・・・状態データ
  • mutations・・・状態を更新する処理
  • getters・・・状態を取得する処理
  • actions・・・非同期処理(mutationsを呼び出す)

gettersとactionsについては後述します。

またsrc/main.jsファイルを編集してsrc/store/index.jsを読み込むよう修正します。

import Vue from 'vue'
import App from './App.vue'
+ import store from './store'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
  + store,
}).$mount('#app')

Vueコンポーネント(MyCounter.vue)の修正

src/components/MyCounter.vueを次のように修正します。

 <template>
     <div>
         Count: {{ count }}
         <button @click="onclick">Increment</button>
     </div>
 </template>

 <script>
 export default {
-    data () {
-        return {
-            count: 1
-        }
-    },
+    computed: {
+        count() {
+            return this.$store.state.count;
+        }
+    },
     methods: {
         onclick () {
-            this.count++
+            this.$store.commit('increment')
         }
     }
 }
 </script>

VueコンポーネントからStoreにアクセスするにはthis.$storeと記述します。this.$store.state.countで状態(state)にアクセスし、状態を更新するmutationsを実行するにはcommitメソッドを使います。

ブラウザでサーバ(localhost:8080)にアクセスして正しく動作することを確認しておいてください。

mapGetters、mapMutationsによるリファクタリング

Vuexには以下のヘルパーメソッドが用意されています。

  • mapState
  • mapMutations
  • mapGetters
  • mapActions

上記のヘルパーメソッドを使うとVueコンポーネントの記述がシンプルになります。

ここではmapGettersとmapMutationsを利用するように修正してみます。

まずはsrc/store/index.jsを修正します。Storeファイルにgettersを追加します。

 import Vue from 'vue'
 import Vuex from 'vuex'

 Vue.use(Vuex)

 const store = new Vuex.Store({
     state: {
         count: 0
     },
+    getters: {
+        count: state => state.count
+    },
     mutations: {
         increment (state) {
             state.count++
         },
     },
 })

 export default store

ここでgettersは単純にstateを返却していますが、gettersはStoreの状態を算出(フィルタリングなど)する用途に使えます。

次にsrc/componetns/MyCounter.vueを修正します。

<template>
    <div>
        Count: {{ count }}
-        <button @click="onclick">Increment</button>
+        <button @click="increment">Increment</button>
    </div>
</template>

<script>
+import { mapGetters, mapMutations } from 'vuex'
+
export default {
    computed: {
-        count() {
-            return this.$store.state.count;
-        }
+        ...mapGetters(['count'])
    },
    methods: {
-        onclick () {
-            this.$store.commit('increment')
-        }
+        ...mapMutations(['increment'])
    }
}
</script>

mapMutationsやmapGettersは戻り値にオブジェクトを返すので、オブジェクトスプレッド演算子を合わせて利用します。また引数には利用したい処理を定数の配列で渡すようにします。

ブラウザでサーバ(localhost:8080)にアクセスして正しく動作することを確認しておいてください。

Part 3 非同期処理(Actions)の実装(10min)

Storeの修正

続いて非同期処理を実装してみます。ここではボタンを押してから2秒後にインクリメントするIncrement Asyncボタンを実装します。

まずはsrc/store/index.jsを修正します。

 import Vue from 'vue'
 import Vuex from 'vuex'

 Vue.use(Vuex)

 const store = new Vuex.Store({
     state: {
         count: 0
     },
     getters: {
         count: state => state.count
     },
     mutations: {
         increment (state) {
             state.count++
         },
     },
+    actions: {
+        incrementAsync ({ commit }) {
+            setTimeout(() => commit('increment'), 2000)
+        }
+    }
 })

 export default store

新たにactionsを定義しています。actionsではmutationsをcommitするようにします。

actionsはWeb APIの呼び出しなどに適しています。

Vueコンポーネント(MyCounter.vue)の修正

続いてsrc/components/MyCounter.vueを修正します。

<template>
    <div>
        Count: {{ count }}
        <button @click="increment">Increment</button>
+        <button @click="incrementAsync">Increment Async</button>
    </div>
</template>

<script>
import { mapGetters, mapMutations } from 'vuex'

export default {
    computed: {
        ...mapGetters(['count'])
    },
    methods: {
+        incrementAsync () {
+            this.$store.dispatch({
+                type: 'incrementAsync'
+            })
+        },
        ...mapMutations(['increment'])
    }
}
</script>

新たにincrementAsyncメソッドを追加しています。incrementAsyncメソッドではStoreに対してdispachメソッドを呼ぶことでactionsを呼び出しています。

ブラウザでサーバ(localhost:8080)にアクセスして正しく動作することを確認しておいてください。

mapActionsによるリファクタリング

続いてmapActionsを使ってリファクタリングしてみましょう。src/components/MyCounter.vueを修正します。

<template>
    <div>
        Count: {{ count }}
        <button @click="increment">Increment</button>
        <button @click="incrementAsync">Increment Async</button>
    </div>
</template>

<script>
-import { mapGetters, mapMutations } from 'vuex'
+import { mapGetters, mapMutations, mapActions } from 'vuex'

export default {
    computed: {
        ...mapGetters(['count'])
    },
    methods: {
-        incrementAsync () {
-            this.$store.dispatch({
-                type: 'incrementAsync'
-            })
-        },
+        ...mapActions(['incrementAsync']),
        ...mapMutations(['increment'])
    }
}
</script>

ブラウザでサーバ(localhost:8080)にアクセスして正しく動作することを確認しておいてください。

まとめ

Vuexを使ってVue.jsアプリケーションの状態管理を試してみました。Vuexの詳細については公式のマニュアルを読めば大体わかります。

https://vuex.vuejs.org/ja/

以降はVue Routerを理解して、Nuxt.jsの学習を進めていくと良いでしょう。