本文書では、できるだけシンプルな例を通してVuexの説明を行っていきます。Vuexをまだ使ってことがないという入門者の人また下記の図がわからないという人を対象にしています。

vuex diagram
vuex diagram

Vuexとは

Vuexはすべてのコンポーネントでデータを一元管理するための仕組みです。

Vuexはstate management(状態管理)という言葉で説明されているため最初は状態という言葉にこだわらずデータ共有管理という名前で考えたほうがわかりやすいかもしれません。

Vuexがない環境ではコンポーネント間のデータの受け渡しには、propsや$emitを使用して行います。しかし、コンポーネント間でのデータ受け渡しが頻繁に行われたり階層が増えてくるとporpsや$emitでのデータ管理が難しくなります。

その問題を解決するための仕組みがVuexです。Vuexという一つの入れ物にデータを入れることでどのからでもそのデータへのアクセスが可能になります。

Vuexでのデータ管理のイメージ
Vuexでのデータ管理のイメージ

vue-cliによるプロジェクトの作成

vuexを使用する環境を構築するためにvue-cliを利用します。


$ vue create vue-learning

vue-cliのインストールの詳細は説明しませんが、Manually select featuresでBableとVuexだけ選択してインストールを行います。

vue-cliを使用しない場合は、vueとは別にvuexをインストールする必要があります。npmを使っている場合は、package.jsonの中を見てvuexがインストール済みかどうか確認してください。

Vuexの基礎

Vuexを使ってHello Vuex

Vuexを使う場合と通常の方法を比較するために両方の方法を使ってHello Vueをブラウザ上に表示させてみましょう。

まずはこれまでの方法で表示させるためにApp.vueファイルに下記を記述します。


<template>
  <div id="app">
    <h1>{{ message }}</h1>
  </div>
</template>

<script>
export default {
  name: 'app',
  data: function(){
    return {
      message : 'Hello Vue'
    }
  }
}
</script>
これまでの方法でHello Vue
これまでの方法でHello Vue

つぎにVuexを使用してHello Vuexを表示させます。vue-cliでインストールする際にVuexも選択したので何の設定もなくすぐにVuexを使用することができます。store.jsファイルを開き、stateにmessageを追加します。

store.jsファイルの中にはstate以外にもmutations、actionsがありますがこれはのちほど説明を行います。

Vuexのstateはコンポーネントのdataと同じものだと考えてください。dataとは異なる点があるとすれば、Vuexのstateに追加するとすべてのコンポーネントからアクセスが可能となる点です。

dataはコンポーネントファイルの中で定義され、その中でのみ使えるデータを扱う時の名前、stateはVuexでデータを扱う時の名前と覚えてください。。

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

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
  	message: 'Hello Vuex'
  },
  mutations: {
  },
  actions: {
  }
})

しかし、すべてのコンポーネントからアクセスできるとはいえApp.vueのtemplateタグでこれまでのように{{ message }}とすればアクセスできるものではなりません。App.vueからのアクセスするためには$store.state.messageと記述する必要があります。

$store.state.messageは少し長いですが、$storeという大きな入れ物の中にされにstateという入れ物があり、その中にmessageが入っているイメージを持つことができます。


<template>
  <div id="app">
    <h1>{{ $store.state.message }}</h1>
  </div>
</template>

ブラウザでアクセスし下記が表示されれば、Vuexの管理下にあるstateのmessageを使ってHello Vuexを表示することができています。

Vuexを使ってHello Vuex
Vuexを使ってHello Vuex

これまでの方法とVuexを利用した方法でmessageを表示させてみましたが、Vuexにデータを保管したからといって処理が難しくなったということはないと思います。

Vuexとこれまでの方法
Vuexとこれまでの方法

Vuexではデータを集中管理しているので、どのコンポーネントから同じようにアクセスすることができます。確認するためにApp.vueに子コンポーネントを作成してそこからアクセスできるか確認してみます。HelloVueという名前でコンポーネントを作成し、App.vueファイルにimportしています。


<template>
  <div id="app">
    <h1>{{ $store.state.message }}</h1>
    <HelloVuex></HelloVuex>
  </div>
</template>

<script>
import HelloVuex from './components/HelloVuex.vue'

export default {
  name: 'app',
  data: function(){
  	return {
  		message : 'Hello World'
  	}
  },
   components: {
    HelloVuex
  }
}
</script>

HelloVue.vueファイルに下記を記述します。Vuexのmessageにアクセスする方法は子コンポーネントでも親コンポーネントでも同じ$store.state.messageです。アクセスの仕方を見てもVuexは独立したデータ保管場所であることがわかるかと思います。


<template>
  <h2>{{ $store.state.message }}</h2>
</template>
<script>
export default {
  name: 'HelloVuex'
}
</script>

子コンポーネントからも$store.state.messageにアクセスできるため下記のようにHello Vuexが2つ表示されます。

子コンポーネントでHello Vuex
子コンポーネントでHello Vuex

computedプロパティを利用してアクセス

{{ $store.state.messag }}を使ってVuexの中のstateを表示させることができましたが、templateタグの中で{{ message }}を使って表示させるためにはcomputedプロパティを利用します。


<template>
  <div id="app">
    <h1>{{ message }}</h1>
  </div>
</template>

<script>

export default {
  name: 'app',
  computed : {
  	message : function(){
  		return this.$store.state.message
  	}
  }
}
</script>

今後はcomputedプロパティを利用して、Vuexのstateへのアクセスを行います。stateのアクセス方法にはgettersというものもありますが、それは後ほど説明します。

オブジェクトデータの表示(これまでの方法)

Vuexのデータへのアクセス方法の理解をさらに深めるためにVuex管理下のオブジェクトデータへのアクセス方法を確認します。まずはこれまでの方法で説明を行い、その後Vuexを使った場合の方法を説明します。

ユーザ情報が入ったusersオブジェクトを利用し、ユーザ情報をリスト化するといったシンプルなものです。

これまでの方法では、dataにusersオブジェクトを設定すると、v-bindを使って子コンポーネントへdataを渡します。


<template>
  <div id="app">
    <UserList v-bind:users="users"></UserList>
  </div>
</template>

<script>

import UserList from './components/UserList.vue'

export default {
  name: 'app',
  data : function(){
  	return {
  		users:[
	  		{name: 'John', email:'john@example.com'},
	  		{name: 'Merry', email: 'merry@facebook.com'},
	  		{name: 'Ken', email: 'ken@amazon.com'}
		  ]
  		}
  	},
  components:{
  	 UserList
	}
  }

</script>

子コンポーネントでは、propsを使って渡されたdataを受け取りv-forで展開します。


<template>
  <ul>
  	<li v-for="user in users">{{ user.name }} ({{ user.email }})</li>
  </ul>
</template>

<script>
export default {
  props : ['users']
}
</script>

ブラウザには下記のようにユーザ一覧が表示されます。

これまでの方法でユーザ一覧
これまでの方法でユーザ一覧

オブジェクトデータの表示(Vuexを利用)

今度はVuexを利用してみましょう。usersオブジェクトをstore.jsに移動します。


state: {
	users:[
  		{name: 'John', email:'john@example.com'},
  		{name: 'Merry', email: 'merry@facebook.com'},
  		{name: 'Ken', email: 'ken@amazon.com'}
	]
}

usersオブジェクトをVuexに移動させたので、App.vueファイルではusersオブジェクトが必要でなくなったので削除を行います。またusersオブジェクトがないためv-bindも必要ありません。


<template>
  <div id="app">
    <UserList></UserList>
  </div>
</template>

<script>

import UserList from './components/UserList.vue'

export default {
  name: 'app',
  components:{
  	 UserList
	}
  }
</script>

子コンポーネント側ではpropsではなくcomputedプロパティを使ってVuexのstateのusersにアクセスを行い、その値を元にユーザ一覧を表示することができます。


<template>
  <ul>
  	<li v-for="user in users">{{ user.name }} ({{ user.email }})</li>
  </ul>
</template>

<script>
export default {
  computed :{
  	users : function(){
  		return this.$store.state.users
  	}
  }
}
</script>

v-bindやpropsの記述がなくなったことからも先程のmessageの例よりもVuexを使う場合と使わない場合の違いが明確になりVuexのデータ管理がこれまでとは異なることが理解できたかと思います。

Vuex内にあるデータの更新方法

Vuex管理下のstate(データ)へのアクセス方法がわかったので、次はVuexで管理されているstateの更新方法を確認します。

store.jsの中のstateにcountを追加し、その値を0とします。


export default new Vuex.Store({
  state: {
  	count: 0
  },
  mutations: {
  },
  actions: {
  }
})

通常はコンポーネント内のメソッドを使ってデータ更新を行うので、App.vueのmethodsに更新用のメソッドを追加し、下記のように直接実行したい気持ちになりますが、Vuexのstateに対して直接の更新は行ってはいけません。直接更新することは可能です。


this.$store.state.count++

stateの更新を行うためには、mutations(ミューテーション)を使う必要があります。store.jsにcountの数を増やすmutationsを追加します。mutationsはmethodsと同じものと考えてください。

dataはVuexではstate、methodsはVuexではmutationsとなります。今後も聞き慣れない新しい名称が出てくるため最初は圧倒されますが、使ううちに慣れるものなので安心してください。またmutateには変更という意味があり、その名詞形のmutationsが使われています。

export default new Vuex.Store({
  state: {
  	count: 0
  },
  mutations: {
    increment : function(state) {
      state.count++
    }
  },
  actions: {
  }
})

mutationsに追加すれば追加したincrementを実行すればいいというわけではなく、mutationsを実行するためにはcommitを経由して行わなければなりません。


//下記のように実行できそうだがダメ
this.$store.increment

// commitを使ってmutationを実行
this.$store.commit('increment')

実際にボタンをつけてクリックするとcountの数字が1増えるるというコードは下記のようになります。


<template>
  <div id="app">
  	<p><button v-on:click="increment">UP</button>
    <h1>Count:{{ count }}</h1>
  </div>
</template>

<script>

export default {
  name: 'app',
  methods: {
  	increment : function(){
  		this.$store.commit('increment')
  	}
  },
  computed : {
  	count : function(){
  		return this.$store.state.count
  	}
  }
}
</script>

ブラウザでUPボタンを押すとcount数が増えていきます。

mutationをcommitしてcount更新
mutationをcommitしてcount更新

commitには値を渡すことができ、payloadと呼ばれます。先程はclickで1つ値が増えましたが、一度に10増やしたい場合は10をpayloadに設定するとVuexのmutationsに設定した値を渡すことができます。


  methods: {
  	increment : function(){
  		this.$store.commit('increment',10)
  	}
</script>

mutation側でもpayloadを取得できるように引数を増やす変更が必要です。


  mutations: {
    increment : function(state, number) {
      state.count = state.count + 10
    }
  }

これでボタンを押すと10アップするプログラムに変更できます。

Actionsを使ってstateを更新

Vuexでは、mutationsに類似したものにactions(アクション)があります。actionsもmutationsのようにstateを更新するために使用されるのですが、大きく2つの違いがあります(マニュアル記載)。

  1. actionsはstateを変更するのではなくmutationsをcommitする
  2. actionsには非同期の処理を入れることができる

1についてはactionsを使用してcountの値を1増やしたい場合の例を使って説明します。

countを増やしたい場合は直接countを更新するのではなくactionsの中でcommitを使ってmutationを実行し、mutationでcountを更新させます。つまりstateの値をactionsで更新する場合は必ずmutationsを経由して行います。

actionsにはビジネスロジックを記述し、mutationsではstateを更新する処理を記述します。commitを使ってmutationsを実行するのではなくシンプルな処理でもactionsを通してmutationsを実行することが推奨されているようです。actionsの中では複数のmutationsを実行することも別のactionsを呼ぶことも可能です。

下記がactionsを追加したコードです。actionsの中でcommitが実行されています。


mutations: {
	increment : function(state) {		
		state.count++
    }
},
actions: {
	incrementOne: function(context){
		context.commit('increment')
	}
}

contexはstoreインスタンスが持つプロパティ、メソッドを保持するオブジェクトです。context.state.countでcountの値を取得することもできます。state, commit以外にもdispatch, gettersも持っています。

contextの中でcommitしか使わないのであれば下記の記述も可能です。


actions: {
	incrementOne: function({commit}){
		commit('increment')
	}
}

mutationsを実行するのは、commitでしたが、actionsを実行するためにはdispatchメソッドを使用します。


  methods: {
  	increment : function(){
  		this.$store.dispatch('incrementOne')
  	}
  },

(2)についてですが、actionsには非同期の処理を入れることができるということはmutationsには非同期処理を入れることができないということを意味します。

実際にはmutationsに非同期処理は入れても動作するので、mutationsでは非同期処理を入れないというのが正しい説明です。stateもmutationsを使わず直接更新ができたようにactionsでもstateを直接更新ができないわけではありません。Vuexは複数のコンポーネントから共有されていることもあり、同時に複数からの処理を正確に処理するためには守らなければいけないルールがあると認識しておく必要があります。

非同期処理というのはaxiosを使ってサーバからデータを取得するような場面で頻繁に使われる処理です。非同期処理のイメージをもってもらうためにsetTimeout関数を使うと下記のような処理になります。3秒後にmutationsのincrementが実行されcount数が1増えます。


actions: {
	incrementOne: function(context){
	    setTimeout(() => {
	      context.commit('increment')
	    }, 3000)
  
	}
},

まとめ

ここまでの説明を通して下記の図にあるActions, Mutations, Stateの説明をすべて行いました。下記の図が理解できていればVuexの概念はほぼ理解できていると思います。

理解を確認するため、文書の中で度々出てきたcountの例を元に下記の図の流れを追ってみましょう。

  1. ユーザがボタンをクリックするとclickイベントによりローカルメソッドが実行され、そのメソッドの中でdispatchメソッッドが実行されます。
  2. dispatchによりActionsが実行され、Actionsの中のcommitメソッドによりmutationsが実行されます。
  3. mutationsはstateであるcountの値を変更します。
  4. アップされた値は、ブラウザに再描写され、ユーザはアップしたcountの値を確認することができます。
vuex diagram
vuex diagram

次回は本文書では触れていないGetterやmapstateの説明を行います。