LoginSignup
17
22

More than 3 years have passed since last update.

30分で基礎がしっかりわかる【Vuex】-入門

Last updated at Posted at 2019-10-11

はじめに

皆さん、こんにちは!Webシステム開発エンジニアの蘭です!
今日は【Vuex】について語りたいと思います。

Vuexって何?

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

何故Vuex?

Vueで足りるんじゃない?
アプリを構築している中、最初はコンポーネント内だけでdataを操作してたが、コンポネントが多くなってくると、コンポーネントで共有して使うデータが現れます。その時思いついたのが、Vueの$emitやpropsを使うことでコンポーネント間のデータ共有問題を解決してました。

しかしアプリが大きくなるに連れ、コンポーネントが更に多くなり、共有データが300以上に増加、もうこのコンポーネントのデータはどのコンポーネントから持ってきたのかが分からない!:scream:
まさに開発に連れ、地獄の道へと進んでしまってたのです。

その時に、現れたのがVuexでした。

Vuexっていつ導入すべきなのか?

ここで想像してみましょう。
例えばコンポネントは一つの店だとします。
Aコンポーネントはバナナしか在庫がなく、Bコンポーネントはりんご、Cコンポーネントはスイカ等、それぞれ一種類の果物しか預かってません。

それで各コンポーネントがフルーツパフェを作りたい時に、コンポーネントAはBにりんごを買ってきたり、BはAからバナナを入荷してましたが、なにかややこしいですよね。
それでVuexの考え方はコンポネントで皆使う共有の果物は全部Storeという多きな倉庫に入れといて、他の果物が欲しいコンポネントは倉庫から入荷しましょうという改善方法が生まれました。
その倉庫がVuexでの「Store(ストア)」です
めでたし、めでたし:smiley:

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

共有データを一つのStoreに管理する
alt

Vuexってどんな時に使うといいの?

・アプリケーション全体で使用されるデータ→Vuexで管理する
・コンポーネントの内部のみで使用されるデータ→dataオプションで管理する

実例:Vuexのシンプルなストア(倉庫)

・以下の例はVuexストア(倉庫)から変数countを取得します。

・コンポーネントからVuexストア共有変数countをstore.state.countで取得。
・VuexではStoreの共有変数countを直接変更できない為、
・対策:ストア共有変数を変更する関数increment()をストア内に準備し、コンポーネントではボタンを押した後、store.commit('increment')経由で共有変数countを間接的に更新する。

See the Pen Vuex_Simple_Store_Demo by Uramaya (@uramaya) on CodePen.

要するに:ストアと単純なグローバルオブジェクトの違い

・ストアの状態を直接変更することはできない。明示的にmutationsをコミットすることによってのみストアの状態を変更する。これによりすべての状態変更に追跡可能な記録を残すことが保証される。

Vuexをやってみよう!

Vuexのインストール

・通常開発ではnpmやyarnでインスタンスします。
  ※前提:Vueのインストール済み

vuex_npm_install.
npm install vuex --save
vuex_yarn_install.
yarn add vuex

・npmやyarnでvuexをインストールの方、
 以下のVuexを明示的に導入が必要。

import_Vuex.
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

・試しであれば、cdnでもOK

vuex_cdn.
https://cdnjs.cloudflare.com/ajax/libs/vuex/2.0.0/vuex.js

Vuexの概念

以下の内容はこちらを参照しています。

Vuexのストアは構成要素として5つの概念を持つ
【state】, 【mutations】, 【getters】, 【actions】, 【modules】
以下は上記の5つの概念について説明します。

alt

 1.【state】

  ・ストアで管理する状態(共有変数、データ)。コンポーネントのdataみたいにデータを保存する場所。
  ・gettersから参照され、更新はmutationsで行う

ストアから状態を取り出す`一番シンプルな方法は、算出プロパティ (computed)で取得しまた返すことです。

・こちらは直接'store.state.count'でストア状態を持ってきてますが、

・開発で以下の欠点があります。

モジュールを使うとき、ストアの状態を使っている全てのコンポーネントをインポートしなければなりません。(ストア状態を共有してるモジュールが100個あった場合、地獄に落ちます。)

See the Pen Vuex_Store_Example by Uramaya (@uramaya) on CodePen.

・上記解決方法:ルートコンポーネントに store オプションを指定

これですべての子コンポーネントにストアを共有する事ができます。

index.vue
new Vue({
  el: '#app',
   //ルートインスタンスに store オプションを渡す
   //★これをすることで、this.$store で各コンポーネントから参照することができます。
   store,
});

See the Pen gOOpGxN by Uramaya (@uramaya) on CodePen.

mapState ヘルパー:算出プロパティ(Computed)で宣伝方法の改善

・以下を見るとわかりますが、算出プロパティを全て宣伝するのは冗長です。

Before:元の算出プロパティ(Computed)宣伝方法
const Counter = {
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
      return this.$store.state.count
    }
  count2 () {
      return this.$store.state.count2
    }
  count3 () {
      return this.$store.state.count3
    }
   ...
  }
}
After:MapState使用後.
computed: mapState({
    count:  'count',  // count: state => state.countと同義
    count2: 'count2', // count2: state => state.count2と同義
    count3: 'count3'  // count3: state => state.count3と同義
  })

・npmやyarnでvuexをインストールの方、魔法の言葉を忘れずに

mapState.
import {mapState} from 'vuex'

 2.【getters】

  ・state内の状態をもとに算出した値を返す関数が書かれる場所
  ・stateのデータを加工して表示
  ・state加工するので、最初の引数はstate
  ・状態をフィルタリング、カウントした値を返す

See the Pen Vuex_Store_Example_getters by Uramaya (@uramaya) on CodePen.

 3.【mutations】

  ・stateを更新する関数が書かれる場所
  ・stateの更新はしない
  ・第一引数は必ずstate, それ以降の引数はpayload
  ・state状態を更新する際は必ずcommitを使用


See the Pen
Vuex_Store_Example_mutations
by Uramaya (@uramaya)
on CodePen.


 4.【actions】

  ・非同期処理や外部API通信する場所
  ・actionで非同期処理を開始→
   実際stateの更新はmutationsをcommitで実行する
    ⇛actionでstateの更新は行わない
    ⇛最終的にmutationsにデータをコミットする関数
    ⇛commitは同期でなければならない

Actions処理

以下の例でActions処理の過程を見てみよう。

全体の処理の流れとしては、コンポーネントからdispatchでアクションを呼び出し、アクション内で外部APIなどからの非同期処理を行った後、commitでミューテーションを使いステートを更新するという流れとなります。
上記はこちらから引用。

・コンポーネントからストアの処理を呼び出すメモ:
  MutationsはCommit / Actionsはdispatch

コンポーネント内のmethodsにActionsをdispatchで実行.
 methods: {
    increment(plus) {
   //plusは引数
      this.$store.dispatch('incrementAsync', plus);
      this.$store.dispatch('warningAsync');
    }
  },
その後、ストアのActionsでcommitを呼び出します.
  actions:{
    incrementAsync({ commit }, plus) {
      setTimeout(()=>{
        alert("これが非同期処理です。");
        commit('increment', plus)
      }, 5000) //非同期で五秒遅らせる事ができる。
    },
    warningAsync({ commit }) {    
        commit('warning')
    }
  },
そして、commitでMutationsからステートを変更します.
  mutations: {
    //★ここでstate状態を変更する関数を用意
    increment (state, plus) {
      state.total += plus
      state.warning_show = false
    },
    warning (state) {
      state.warning = "5秒お待ち下さい。"
      state.warning_show = true
    }
  }, 

それでは実装してみよう。

非同期処理

See the Pen Vuex_Store_Example_actions by Uramaya (@uramaya) on CodePen.

API通信

・今回非同期のAPI通信では、ライブラリ「axios」を使います。
・async関数を使ってみる(必須ではない):

async関数:
async関数・メソッドはメソッドの冒頭に async をつけることで、 await が使え、awaitは必ずaxios.get等通信処理が終了し、responseもしくはerrorが返ってきた後に、awaitの処理を実行します。

See the Pen ExxVjym by Uramaya (@uramaya) on CodePen.

 【state】、【getters】、【mutations】、【actions】を使ってTodoListを作ってみよう!

ここまで読んでいただき、ありがとうございます!
ではモジュールを理解する前に、以下のコードで遊んでみてください。

See the Pen Vue + Vuex Demo by Arpit Gupta (@arpitg) on CodePen.

 5.【modules】

  ・上記4つのストア構成要素を分割したもの。
  ・アプリケーションの肥大化に伴い大きくなるストアに対し、見通しをよく保つためにモジュールに分割する。

以下の記事を参考しました。
  Vuex公式-モジュール

開発に連れ、コードが膨大する中、全て何百個のstate, getters, mutations, actionsを一つのファイルにまとめるのは事実上管理不可能です。
ここで解決法が【モジュール】です。
モジュールとは元々凄く膨大なstateやgetters等を小さなモジュール単位に分割して、管理することです。
もちろん、これで保守、修正、管理が便利になります。

モジュールの使い方を見てみよう!

・1.同一ファイルでモジュール分け
同一ファイルでモジュール分け.
//モジュールA
const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

//モジュールB
const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
}

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

store.state.module_a // -> `moduleA` のステート
store.state.module_b // -> `moduleB` のステート
・2.違うファイルでモジュール分け(現場)

・モジュールの分け方について以下を参考しました。
  Vuex の Modules 機能
  vuexでmoduleを分ける方法と注意点

 ・(1)モジュール:「superFunction」と「header」を storeに登録
/store/index.js
import Vue from "vue";
import Vuex from "vuex";

import superFunction from "./superFunction";
import header from "./header";

Vue.use(Vuex);

const store = new Vuex.Store({
  modules: {
    superFunction,
    header
  }
});

export default store;
 ・(2)モジュールの指定を明確にする為、 namespaced: true にすることを忘れずに

 ・namespacedについて以下を引用しました。
   Vuexのストアをモジュールに分割する

・Vuex名前空間の概念:【namespaced: true】 とは

namespaced:オプションをtrueにすることで、それぞれのモジュールに名前空間を与えて呼び出し方を管理することができる。

ただstateについては、namespaced: trueの有無は関係なく一律で$store.state.(モジュール名).data.messageのように呼び出す。

その他のmutation, action, getterはnamespaced: trueを与えなかった場合はモジュールを使用せずグローバルに登録したときと同じように呼び出せる。

もしnamespaced: trueを与えていない複数のモジュール内で名前が被った場合、mutationとactionはそれぞれが同時に実行される。
getterは以下のようなエラーが発生する。

[vuex] duplicate getter key: greetingC.

namespaced: trueを与えたmutation, action, getterは、名前の前にモジュール名/を付与して呼び出すことで、上記のエラーを避けられます。

console.log(this.$store.getters['moduleA/greeting']
console.log(this.$store.commit('moduleA/greeting')
console.log(this.$store.dispatch('moduleA/greeting')
// getterは[], mutationとactionは()で囲むので注意

・モジュールを作ってみよう

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

const getters = {
  appNumber(state) {
    return state.appNumber;
  }
};

const actions = {
  changeNumber({ commit }, val) {
    commit("changeNumber", val);
  }
};

const mutations = {
  changeNumber(state, value) {
    state.appNumber = state.appNumber + value;
  }
};

const superFunction = {
  namespaced: true, // 忘れずに
  state,
  getters,
  actions,
  mutations
};

export default superFunction; //モジュールの名前
 ・(3)コンポネントでモジュール:superFunctionを使う

以下の注意点:
・getter と action の参照の仕方が変わる
・this.$store.getters["moduleName/getterName"]
・this.$store.dispatch("moduleName/actionName")

/components/AppMain.vue
<template>
  <main>
    {{appNumber}}
    <Controller :changeNumber="changeNumber"/>
  </main>
</template>

<script>
import Controller from "./Controller";
export default {
  components: {
    Controller
  },
  computed: {
    appNumber() {
      return this.$store.getters["superFunction/appNumber"];
    }
  },
  methods: {
    changeNumber(val) {
      this.$store.dispatch("superFunction/changeNumber", val);
    }
  }
};
</script>

・上記の実際のソースコードを見て試してみよう【Codesandbox】
https://codesandbox.io/embed/w67wklz0pk?fontsize=14

おまけに

・npmやyarnでVuexをインストールした方は、必ずuseでVuexの参照をしてください。

/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import App from './App.vue'

Vue.use(Vuex); //忘れずに

const store = new Vuex.Store({
......

・Vuexで開発する際に、以下の記事も参考にできればと思います。
  Vuexを用いた開発プロジェクト用にガイドラインを作成した話
  メンテナンスしやすいVueComponentを設計するために気をつけていること

・VueとVuexの練習
 30minくらいで学ぶVue.jsとVuex
 簡単なTODOアプリで Vue + Vuex を学んでみよう
 簡単なTODOアプリソースコード(Github)

・Vueの基本概念
 10分で基礎がわからるVue.js-入門

最後に

今回はVuexについて基本の使い方を紹介しました。
自分のニーズに沿って、是非Vuexを開発で使って見てください!:D

17
22
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
17
22