92
96

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Vuex の使い方を勉強してみた

Last updated at Posted at 2019-07-01

はじめに

Qiita 初投稿です:relaxed:

Vuex について勉強した際のメモをまとめました。
まだWeb開発初心者のため、単語や言い回しなどおかしなところがあるかもしれません。

間違った記載がありましたらご教授いただけると嬉しいです!

Vuex とは

Vue を用いたアプリケーションの開発では、コンポーネント間でのデータのやりとりが頻繁に発生する。
コンポーネント間でデータの整合性を保つためには 各コンポーネントで値渡しの処理を記述する必要があり、ソースの可読性とデバッグ効率が低下しやすい。

Vuex は、Vueアプリケーションにおけるデータの状態管理を一元化して開発効率を上げることを目的としたライブラリである。
Vue アプリケーションで扱うデータセットを Store と呼ばれる領域で一元管理することで、各コンポーネントは Store にアクセスすれば常に共通の値を参照することができるようになる。

また、Store のデータに対する操作を予め定義しておけるので、予期しない操作の防止や保守性・可読性の向上が見込める。

Vuex / Store の定義

Vuex を Vue アプリケーションで使用する際は以下のように宣言し、Store を定義する。

store/index.js
"use strict"

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

// Storeを生成
const store = new Vuex.Store({
  state: { ... },
  getters: { ... },
  mutations: { ... },
  actions: { ... }
});
export default store;
main.js
import Vue from "vue";
import store from "store";

// Vueインスタンスの定義時に、Store情報を組み込む
new Vue({
  el: '#app',
  store,
  render: h => h(App)
});

store の宣言で state , getters , mutations , actions という項目があるが、これらは Store が保持するデータ項目や、Store 上のデータを外部(コンポーネント等)から操作するための関数を定義する項目である。

state , getters , mutations , actions

Store の作成時に定義できる項目は下記の4つである。

項目名 概要
state Store で管理するデータ項目の定義
getters state 内のデータの状態から算出される値(≒算出プロパティ)
mutations state のデータを直接操作するための関数(非同期処理は定義不可)
actions mutations の操作を各コンポーネントから呼び出すために使用する関数(非同期処理を定義可)

↓ 定義のイメージ

const store new Vuex.Store({
  state: { ... },
  getters: { ... },
  mutations: { ... },
  actions: { ... }
});

それぞれの項目は用途によって使い分けされるので、順番に説明する。

state ( Store で管理するデータ項目の定義 )

Vuex の Store で管理するデータ項目を定義する。
ここに定義したデータは Vueアプリケーション内の各コンポーネントから適宜取得・更新することができる。

state 定義の例

以下の例では、データ項目 count を初期値0で定義している。

store/index.js
//  Store 定義
const store = new Vuex.Store({
  state: {
    count: 0
  },
});

export default store;

また、次のように store から state を切り離して定義することもできる。

store/index.js
const state = {
  count: 0
};

//  Store 定義
const store = new Vuex.Store({
  state,
});

export default store;
コンポーネントから state の値を使用

コンポーネントからは、 this.$store.state を経由して値を参照できる。
参照する際は computed を用いることで、その変更を監視できる。

components/Counter.vue
<template>
  <div>{{ count }}</div>
</template>

<script>
export default {
  name: "Counter",
  computed: {
    count () {
      return this.$store.state.count
    }
  }
};
</script>

コンポーネントから呼び出す際は mapState ヘルパーを使用することもできる。
複数の state の項目を利用する際はこちらの方が簡潔に書ける。

store/index.js
//  Store 定義
const store = new Vuex.Store({
  state: {
    count1: 0,
    count2: 0
  },
});

export default store;
components/Counter.vue
<template>
  <div>
    <div>{{ count1 }}</div>
    <div>{{ count2 }}</div>
  </div>
</template>

<script>
import { mapState } from "vuex"

export default {
  name: "Counter",
  computed: {
    ...mapState([
      "count1", // 注意)プロパティ名は ' または " でくくる必要がある
      "count2"
    ])
  }
};
</script>
state データの更新・削除について

statethis.$store.state.count = 100 のように__直接更新・削除を行なってはいけない__
基本的に Store 内のデータに対する操作は、後述する mutations に定義する。

getters ( state 内のデータの状態から算出される値(≒算出プロパティ))

getters では state のデータに対する算出プロパティを定義し、各コンポーネントで利用できる。

例えば、TODOリストの未完了のデータ数を取得する関数 doneTodoCount が、以下のように定義されるとする。

components/TodoList.vue
<template>
  <div>{{ doneTodoCount }}</div>
</template>

<script>
export default {
  name: "TodoList",
  computed: {
    doneTodoCount () {
      return this.$store.state.todos.filter(todo => todo.done).length
    }
  }
};
</script>

上記の書き方で目的は果たせるが、他のコンポーネントでこの関数を利用したい場合にはこの関数をコピーするか、共通処理として外部モジュールに切り出してインポートする必要がある。
getters を使用することで、Store 経由で共通の算出プロパティとして使用できるようになる。

getters の定義

getters に定義する関数は第1引数に state をもち、ここから Store のデータにアクセスできる。

store/index.js
// Store 定義
const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, label: '...', done: true },
      { id: 2, label: '...', done: false }
    ]
  },
  getters: {
    // 第1引数に state をもつ
    doneTodoCount: (state) => {
      return state.todos.filter(todo => todo.done).length
    }
  }
  // ...
});

export default store;
コンポーネントから getters の関数を使用

state と同様の形で this.$store.getters に含まれるゲッター関数を computed で監視する。

components/TodoList.vue
<template>
  <div>{{ doneTodoCount }}</div>
</template>

<script>
export default {
  name: "TodoList",
  computed: {
    doneTodoCount () {
      return this.$store.getters.doneTodoCount
    }
  }
};
</script>

mapGetters ヘルパー関数によって参照することもできる。

components/TodoList.vue
<template>
  <div>{{ doneTodoCount }}</div>
</template>

<script>
import { mapGetters } from "vuex"

export default {
  name: "TodoList",
  computed: {
    ...mapGetters([
      "doneTodoCount"
    ])
  }
};
</script>

※getters では同期的な処理のみを記述する。Ajax等の非同期処理を実行したい場合は、後述する actions で定義する。

mutations ( state のデータを直接操作する関数 )

記述中

actions ( mutations の操作 + 非同期処理する関数 )

記述中

モジュール分割による store の切り分け

Store は以下のようにモジュール分割して定義することもできる。

store/moduleA.js
const moduleA = {
  state: { ... },
  getters: { ... },
  mutations: { ... },
  actions: { ... }
};

export default moduleA;
store/moduleB.js
const moduleB = {
  state: { ... },
  getters: { ... },
  mutations: { ... },
  actions: { ... }
};

export default moduleB;
store/index.js
import Vue from "vue";
import Vuex from "vuex";
import moduleA from "./moduleA.js";
import moduleB from "./moduleB.js";

Vue.use(Vuex);

const store = new Vuex.Store({
  modules: {
    moduleA,
    moduleB
  }
});

export default store;

モジュール分割することで Store が肥大化することを防ぎ、またカテゴリ等によって Store を分けて管理できる。
デフォルトでは各モジュールで宣言した getters , mutations, actions はグローバル名前空間に登録されるため、複数のモジュールが同じミューテーション/アクションタイプに反応することになる。

名前空間をモジュール単位で登録したい場合は、モジュールの宣言時に namespaced: true を設定する。

store/moduleA.js
const moduleA = {
  namespaced: true,

  state: { ... },
  getters: { ... },
  mutations: { ... },
  actions: { ... }
};

モジュール分割した Store をコンポーネントから参照する

以下のようなモジュール分割された Store を定義し、コンポーネントから参照してみる。

store/moduleA.js
const moduleA = {
  namespaced: true,
  state: {
    result: undefined
  },
  mutations: {
    setResult(state, data) {
      state.result = data
    },
    clearResult(state) {
      state.result = undefined
    }
  },
  actions: {
    setResult({ commit }, data) {
      commit("setResult", data)
    },
    clearResult({ commit }) {
      commit("clearResult")
    }
  }
};

export default moduleA;
store/moduleB.js
const moduleB = {
  // 以下、moduleA と同じ内容
  namespaced: true,
  state: {
    result: undefined
  },
  mutations: {
    setResult(state, data) {
      state.result = data
    },
    clearResult(state) {
      state.result = undefined
    }
  },
  actions: {
    setResult({ commit }, data) {
      commit("setResult", data)
    },
    clearResult({ commit }) {
      commit("clearResult")
    }
  }
};

export default moduleB;

moduleAmoduleB はそれぞれが

  • データ項目 result
  • データ項目 result を直接編集するミューテーション setResultclearResult
  • ミューテーションを呼び出すためのアクション setResultclearResult

を持つ。

以下は moduleAmoduleB の state項目 result に対して表示・更新・クリアを実行するコンポーネントである。

components/Sample.vue
<template>
  <div class="sample">
    <div class="view">
      <p>state.result の状態</p>
      <div>ModuleA : {{ result_A }}</div>
      <div>ModuleB : {{ result_B }}</div>
    </div>

    <div>
      <p>ModuleA の state 操作</p>
      <input type="text" v-model="input_A">
      <button @click="setResult_A">更新</button>
      <button @click="clearResult_A">クリア</button>
    </div>

    <div>
      <p>ModuleB の state 操作</p>
      <input type="text" v-model="input_B">
      <button @click="setResult_B">更新</button>
      <button @click="clearResult_B">クリア</button>
    </div>
  </div>
</template>

<script>
import { mapState, mapActions } from "vuex"

export default {
  name: "Sample",
  data: function() {
    return {
      input_A: this.result_A,
      input_B: this.result_B
    }
  },

  computed: {
    // mapState の第1引数に名前空間(moduleA, moduleB)を指定し、
    // それぞれの "state.result" を別名(result_A, result_B)で取得
    ...mapState(
      "moduleA",
      { result_A: state => state.result }
    ),
    ...mapState(
      "moduleB",
      { result_B: state => state.result }
    )
  },

  methods: {
    // mapActions の第1引数に名前空間(moduleA, moduleB)を指定し、
    // それぞれのアクションを別名で取得
    ...mapActions(
      "moduleA", 
      {
        setResult_A(dispatch) { dispatch("setResult", this.input_A) },
        clearResult_A: "clearResult"
      }
    ),
    ...mapActions(
      "moduleB",
      {
        setResult_B(dispatch) { dispatch("setResult", this.input_B) },
        clearResult_B: "clearResult"
      }
    )
  }
};
</script>

<style scoped>
.sample {
  max-width: 500px;
  text-align: left;
}

.view {
  border: 1px solid black;
  padding: 0 0 10px 10px;
}
</style>

上記の ...mapActions("moduleA", [ ... ]) のように、ヘルパー関数の第1引数に store/index.js で定義した名前空間(moduleA, moduleB)を指定することで、指定したモジュールごとの store を操作できる。

まとめ

Vuex を使うと状態管理がだいぶ楽になることが分かりました。
また、データの扱いがある程度ルール化されているので、初心者にはありがたいです。
最後までご覧いただきありがとうございました!

92
96
2

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
92
96

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?