3
4

More than 3 years have passed since last update.

【vue.js】Vuexを学ぶ

Last updated at Posted at 2020-11-15

Vuexの概念が何となく理解出来たので、ついでに使い方もまとめておく。
自社の開発においても大量のコンポーネントを扱わなければならない中でVuexが必須となっているので、勉強し直した。

Vuex(ビューエックス)とは何か?

Vuex とは何か? | Vuex

ググったら一番最初に出てくるこれを見てもイマイチ分からない人に向け、
vue.jsの基本は一応分かっている前提で説明すると、
スクリーンショット 2020-11-14 19.07.13.png
沢山のコンポーネントがあるそれなりの規模のプロジェクトがあったとする。
さて、この図でComponentAのデータをComponentCやComponentEに渡すにはどうしたらいいだろうか。

親コンポーネントから子コンポーネントへ、
また子コンポーネントから親コンポーネントへのデータのやりとりが出来るのはわかると思う。でも、孫から子へ、それを親に渡し、さらにそれを子から孫へ、みたいなやりとりは手間である。($emitして$emitしてpropsしてpropsして見たいにやるのはスマートではない。)
スクリーンショット 2020-11-14 19.29.39.png
そこでVuexを使うと、グローバル変数のような感じでデータを扱うことが出来るようになると言う感じ(概念)。と言われれば分かり易いのではないだろうか。これで深いコンポーネント同士でのやりとりが出来るようになる。

導入

Vuexのパッケージをインストールする。
vue cliでプロジェクト作成時にセットでインストール済みである場合は必要ない。

npm install vuex

vuex用のファイルを新たに設ける。
ファイル名は何でも良いのだが、store.jsとするのが一般的。

store.js
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex); // vuexをvue全体で使用する宣言

export default new Vuex.Store({ // main.jsで読み込めるようにする
 // 以下で定義したものはどのコンポーネントでも使用出来る
  state: { 
    number: 2
  }
})

あとはmain.jsで読み込むだけ。

main.js
import Vue from 'vue';
import App from './App';
import router from './router';
import store from './store' // store.jsをインポート

Vue.config.productionTip = false;

new Vue({
  el: '#app',
  router,
  store, // store: store,
  components: { App },
  template: '<App/>',
});

stateを使ってみる

stateとは?

文字通りstoreの状態をプロパティーとして返すと言う位置付けになろうか。前述の通り、ここで定義した内容はグローバルに取得する事が出来るようになる。

store.js
export default new Vuex.Store({ // main.jsで読み込めるようにする
 // 以下で定義したものはどのコンポーネントでも使用出来る
  state: { 
    number: 2
  }
})

ComponentAで表示する変数を

ComponentA.vue
<template>
  <p>{{ number }}</p>
</template>

<script>
  export default {
    computed: {
      number() {
        // $storeとする事でどこからでも呼べるようになる
        return this.$store.state.number;
      }
    }
  }
</script>

ComponentEから操作出来る、みたいな事が出来る。

ComponentE.vue
<template>
  <b-button @click="increment">+1</b-button>
</template>

<script>
  export default {
    methods: {
      increment(){
        // $storeとする事でどこからでも呼べるようになる
        this.$store.state.number++;
      },
    },
  }
</script>

gettersを使ってみる

gettersとは?

storeの状態を算出したい時にgettersを定義する事で、算出プロパティーとして使う事が出来る感じ。

store.js
export default new Vuex.Store({ // インスタンスをmain.jsで読み込めるようにする
  state: { // 状態を全体で使用出来るようにする
    number: 0
  },
  getters: {
    counter: state => state.number++, 
  }
})

こんな感じでVuexの中でcomputedのような事が出来る。

ComponentA.vue
<template>
  <p>{{ count }}</p>
</template>

<script>
  export default {
    computed: {
      count() {
        return this.$store.getters.counter; // $store.gettersで呼ぶ
      }
    }
  }
</script>

まぁこの程度であればgettersを使う意味はないのだけど、
よりコンポーネントが複雑化してきた時に真価を発揮するものなのでそこはご容赦頂きたい。

複数のgettersを扱う時にもっと便利に使う事が出来るのが、mapGettersである。

store.js
export default new Vuex.Store({ 
  state: { 
    number: 2
  },
  // 複数定義しているものがあるとする
  getters: {
    doubleCount: state => state.number * 2,
    tripleCount: state => state.number * 3
  }
})

こう書くことも出来るが、

ComponentA.vue
<script>
  export default {
    computed: {
      doubleCount() {
        return this.$store.getters.doubleCount;
      },
      tripleCount() {
        return this.$store.getters.tripleCount;
      },
    }
  }
</script>

mapGettersを用いると一気に整理される。

ComponentA.vue
<script>
import { mapGetters } from "vuex"; // mapGettersをインポートする

  export default {
    // 配列
    computed: mapGetters(["doubleCount", "tripleCount"]),
  }
</script>

配列ではなく、オブジェクトでも良い。

ComponentA.vue
<script>
import { mapGetters } from "vuex"; // mapGettersをインポートする

  export default {
    // オブジェクトでも良い
    computed: mapGetters({
      doubleCount: "doubleCount",
      tripleCount: "tripleCount"
    }),
  }
</script>

必要なgettersだけを取ってくる事が出来るので是非使いこなしたい。
ちなみにこのままだと、computedにmapGetters以外に記述する事が出来ないので、普段使いでは

ComponentA.vue
<script>
import { mapGetters } from 'vuex'

export default {
  computed: {
    // スプレッド演算子(object spread operator)を使って組み込む
    ...mapGetters([
      'doubleCount',
      'tripleCount',
    ])
  }
}
</script>

という感じで使うっぽい。

mutationsを使ってみる

mutationsとは?

stateを使う事でコンポーネント間のデータのやりとりは簡単にはなるけれど、
何でもかんでも自由にやりとりをさせ過ぎると、逆に分かりづらくなったり、追跡、管理が煩雑になる。そこで使用するのがmutationsという事のようだ。

store.js
export default new Vuex.Store({
  state: {
    number: 2
  },
  mutations: {
    increment(state, str) { // 第一引数にstateをとり、実際の変更を記述する
      state.number += str;
    }
  }
})

呼び出す側ではstore.commitでmutationsを指定する。

ComponentA.vue
<script>
methods: {
  increment(){
    this.$store.commit('increment', 2);
  },
},
<script>

stateをあっちこっちで変更する事は思わぬバグを発生させる可能性があるから、
mutationsで扱いましょうという感じだろうか。

また、mutationsにもmapMutationsというヘルパーが存在する。

ComponentA.vue
<script>
import { mapMutations } from "vuex"; // mapMutationsをインポートする

  export default {
    methods: {
      // mapGettersと同じようにスプレット演算子で書ける
      ...mapMutations(["increment","decrement"]),
    },
  }
</script>

ちなみにmutationsは同期的でなければならないというルールがある。
では非同期を扱うにはどうしたらいいのか。

actionを使ってみる

actionとは?

actionはmutationsと異なり、任意の非同期処理を含む事が出来る。
なので、setTimeoutで一定時間後に特定の処理を行うという記述も可能となっている。

store.js

export default new Vuex.Store({ 
  state: { 
    count: 2
  },
  actions: {
    increment({ commit },number) {
      commit('increment', number);
    }
  }
})

store内のactionsでcommitし、コンポーネントでdispatchする。

ComponentA.vue
<script>
methods: {
  increment() {
    this.$store.dispatch('increment', 2); 
  }
},
</script>

非同期でもOK

store.js
actions: {
  incrementAsync ({ commit }) {
    setTimeout(() => {
      commit('increment')
    }, 1000)
  }
}

最後はmapActionsヘルパー、これまで出てきたmapと同じ感覚で使用出来る。

ComponentA.vue

<script>
import { mapActions } from "vuex";

  export default {
    methods: {
      // スプレット演算子で書ける
      ...mapActions(["increment","decrement"]),
    }
</script>

まとめると・・・

・stateでvuexの状態を管理
・gettersでstateの変更を算出
・mutationsでstateの状態を変更、commitで呼び出される
・actionで同期、非同期なデータの処理、必要に応じてcommitする

これらの基本に基づいて、導入するプロジェクト毎にどうVuexを使っていくかを取捨選択していく形になるという事が分かった。

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4