Help us understand the problem. What is going on with this article?

Vuex入門:初心者向けガイド をやってみた

More than 1 year has passed since last update.

以下のページがVuexの勉強でとても参考になりましたので、勉強させていただきました。m(_ _)m

※この投稿にかいてあることは全て↑のリンクにかいてあることと同じです。自分の備忘録として投稿させていただきました。

webpackで環境作成

参考サイトでは@vue/cliで環境作成を行っていましたが、私はwebpackで作成しました。

自分で試したサンプル

bash
yarn init -y
yarn add vue vuex
yarn add -D webpack webpack-cli webpack-dev-server vue-loader vue-template-compiler vue-style-loader css-loader babel-loader @babel/core @babel/preset-env
./webpack.config.js
const VueLoaderPlugin = require("vue-loader/lib/plugin");

module.exports = {
    mode: "development",
    module: {
        rules: [
            {
                test: /\.vue$/,
                loader: "vue-loader"
            },
            {
                test: /\.js$/,
                loader: "babel-loader"
            },
            {
                test: /\.css$/,
                use: ["vue-style-loader", "css-loader"]
            }
        ]
    },
    plugins: [new VueLoaderPlugin()],
    resolve: {
        extensions: [".vue", ".js"],
        alias: {
            vue$: "vue/dist/vue.esm.js"
        }
    },
    devServer: {
        port: 9000,
        contentBase: "./",
        publicPath: "/dist/",
        open: "Google Chrome"
    }
};
./.babelrc
{
    "presets": [
        [
            "@babel/preset-env",
            {
                "targets": {
                    "node": "current"
                }
            }
        ]
    ]
}
./src/index.js
import Vue from "vue"
import App from "./App"
import store from './store'

new Vue({
    store,
    el: "#app",
    template: "<App/>",
    components: { App }
})
./src/App.vue
<template>
  <div id="app">
    <h1>Sample Counter</h1>
    <Counter />
  </div>
</template>

<script>
import Counter from "./components/Counter";

export default {
  components: {
    Counter
  }
};
</script>
./src/components/Counter.vue
<template>
  <div>
    <p>Count: {{ count }} !</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      count: 0
    };
  }
};
</script>
./src/store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        // put variables and collections here
    },
    mutations: {
        // put sychronous functions for changing state e.g. add, edit, delete
    },
    actions: {
        // put asynchronous functions that can call one or more mutation functions
    }
})
./index.html
<!DOCTYPE html>
<meta charset=utf-8>
<title>sample</title>
<script src=dist/main.js defer></script>
<div id=app></div>
bash
yarn webpack-dev-server

この実行環境をもとにガイドを進めていきます。

Screen Shot 2019-07-07 at 4.21.38.png

Store

アプリケーション全体で参照するデータのかたまりの部分です。

store.jsで以下のように設定してみます。

./src/store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        products: [],
        count: 5,
        loggedInUser: {
            name: 'John',
            role: 'Admin'
        }
    },
})

$storeを使ってストアを参照する

storeに全てのデータを集約させるので、<script>タグのdataの部分は削除します

./src/components/Counter.vue
<template>
  <div>
    <p>Count: {{ $store.state.count }} !</p>
  </div>
</template>

<script>
export default {};
</script>

computedを使うことでスッキリ書くことができます。

./src/components/Counter.vue
<template>
  <div>
    <p>Count: {{ count }} !</p>
  </div>
</template>

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

Screen Shot 2019-07-07 at 4.27.33.png

mapState Helper

mapStateを使うことでcomputedをさらに簡潔に書けます

./src/components/Counter.vue
<template>
  <div>
    <p>Welcome, {{ loggedInUser.name }}.</p>
    <p>Count: {{ count }} !</p>
  </div>
</template>

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

export default {
  computed: mapState({
    count: state => state.count,
    loggedInUser: state => state.loggedInUser
  })
};
</script>

また、↑のようにただstoreの値とするだけなら、文字列だけをmapStateに渡すことでさらに簡潔に書けます。
↓で同じ意味となります。

./src/components/Counter.vue
<template>
  <div>
    <p>Welcome, {{ loggedInUser.name }}.</p>
    <p>Count: {{ count }} !</p>
  </div>
</template>

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

export default {
    computed: mapState([
        "count", "loggedInUser"
    ])
};
</script>

Screen Shot 2019-07-07 at 4.35.31.png


computedstore以外の値を渡したい場合は、スプレッド構文を使えばうまくいきます。

./src/components/Counter.vue
<template>
  <div>
    <p>Welcome, {{ loggedInUser.name }}.</p>
    <p>Count: {{ count }} ! Count is {{ parity }}.</p>
  </div>
</template>

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

export default {
  computed: {
    ...mapState([
      "count", "loggedInUser"
    ]),
    parity () {
      return this.count % 2 === 0 ? "even" : "odd";
    }
  }
};
</script>

Screen Shot 2019-07-07 at 4.39.44.png

Getters

vuexのsotregetterを設定すると、computedと同じように扱うことができます。

./src/store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        products: [
            { id: 1, name: "Hoge", stock: 0 },
            { id: 2, name: "Fuga", stock: 3 },
            { id: 3, name: "Piyo", stock: 0 },
        ],
        count: 5,
        loggedInUser: {
            name: 'John',
            role: 'Admin'
        }
    },
    getters: {
        depletedProducts: state => {
            return state.products.filter(product => product.stock <= 0)
        }
    },
})
./src/components/Counter.vue
<template>
  <div>
    <ul>
      <li v-for="(product, i) in depletedProducts" :key="i">
          name: {{ product.name }}
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  computed: {
    depletedProducts() {
      return this.$store.getters.depletedProducts;
    }
  }
};
</script>

Screen Shot 2019-07-07 at 4.54.27.png

mapGetters Helper

こちらもmapGettersというヘルパーが用意されており、簡潔に書くことができます。

./src/components/Counter.vue
<template>
  <div>
    <ul>
      <li v-for="(product, i) in depletedProducts" :key="i">
        name: {{ product.name }}
      </li>
    </ul>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'

export default {
  computed: {
    ...mapGetters([
      'depletedProducts',
      'anotherGetter'
    ])
  }
};
</script>

関数を返すことで、getterに引数を渡すこともできます。

        getProductById: state => id => {
            return state.products.find(product => product.id === id);
        }
./src/store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        products: [
            { id: 1, name: "Hoge", stock: 0 },
            { id: 2, name: "Fuga", stock: 3 },
            { id: 3, name: "Piyo", stock: 0 },
        ],
        count: 5,
        loggedInUser: {
            name: 'John',
            role: 'Admin'
        }
    },
    getters: {
        depletedProducts: state => {
            return state.products.filter(product => product.stock <= 0)
        },
        getProductById: state => id => {
            return state.products.find(product => product.id === id);
        }
    },
})
./src/components/Counter.vue
<template>
  <div>
    product1: {{ getProductById(2).name }}
    <ul>
      <li v-for="(product, i) in depletedProducts" :key="i">
        name: {{ product.name }}
      </li>
    </ul>
  </div>
</template>

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

export default {
  computed: {
    ...mapGetters([
        "depletedProducts", 
        "getProductById",
    ])
  }
};
</script>

Screen Shot 2019-07-07 at 5.06.28.png

Mutations

storeの内容を直接変更してはいけません。
必ずmutationsから変更するようにしましょう。

./src/store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        count: 1
    },
    mutations: {
        increment(state) {
            state.count++
        }
    }
})
./src/components/Counter.vue
<template>
  <div>
    <p>Count: {{ $store.state.count }} !</p>
    <button @click="updateCount">increment</button>
  </div>
</template>

<script>
export default {
  methods: {
    updateCount() {
      this.$store.commit("increment");
    }
  }
};
</script>

Screen Shot 2019-07-07 at 5.20.03.png


パラメータを渡すこともできます

  incrementBy(state, n) {
    state.count += n;
  }
./src/store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        count: 0
    },
    mutations: {
        incrementBy(state, n) {
            state.count += n;
        }
    }
})
./src/components/Counter.vue
<template>
  <div>
    <p>Count: {{ $store.state.count }} !</p>
    <button @click="updateCount">increment</button>
  </div>
</template>

<script>
export default {
  methods: {
    updateCount() {
      this.$store.commit('incrementBy', 25);
    }
  }
};
</script>

Screen Shot 2019-07-07 at 5.23.44.png


オブジェクトをパラメータに渡すこともできる。

    mutations: {
        incrementBy(state, { amount }) {
            state.count += amount;
        }
    }
  methods: {
    updateCount() {
      this.$store.commit('incrementBy', { amount: 25 });
    }
  }

このようなオブジェクトを渡しても大丈夫です。

  methods: {
    updateCount() {
      this.$store.commit({
        type: "incrementBy",
        amount: 25
      });
    }
  }

mapMutations Helper

mapMutationsを使って簡潔に書くことができます。

./src/store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        count: 0
    },
    mutations: {
        increment(state) {
            state.count++;
        },
        incrementBy(state, { amount }) {
            state.count += amount;
        },
    }
})
./src/components/Counter.vue
<template>
  <div>
    <p>Count: {{ $store.state.count }} !</p>
    <button @click="increment">increment</button>
    <button @click="incrementBy({ amount: 2 })">increment + 2</button>
  </div>
</template>

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

export default {
  methods: {
    ...mapMutations([
        "increment", 
        "incrementBy"
    ])
  }
};
</script>

Actions

mutationをcommitするときに、間に入って実行する機能です。
非同期処理を行うときはActionsからcommitします。

こちらが参考になりました
参考:https://vuex.vuejs.org/guide/actions.html#composing-actions

./src/store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        count: 0
    },
    mutations: {
        increment(state) {
            state.count++;
        },
    },
    actions: {
        increment: context => {
            return new Promise(resolve => setTimeout(() => {
                context.commit("increment");
                resolve(context.state.count)
            }, 1000))
        }
    }
})
./src/components/Counter.vue
<template>
  <div>
    <p>Count: {{ $store.state.count }} !</p>
    <button @click="increment">increment</button>
  </div>
</template>

<script>
export default {
  methods: {
    async increment() {
      console.log(await this.$store.dispatch("increment"));
    }
  }
};
</script>

Screen Shot 2019-07-07 at 6.12.14.png


        increment: context => {
            return new Promise(resolve => setTimeout(() => {
                context.commit("increment");
                resolve(context.state.count)
            }, 1000))
        }

contextは以下それぞれ受け取ることができます。

  • context.commit
    • mutationcommitする
  • context.state
    • stateを取得
  • context.getters
    • gettersを取得する

なのでこのように書くと簡潔になります

        increment: ({ commit, state, getters}) => {
          // ...
        },

mapActions Helper

./src/store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        count: 0
    },
    mutations: {
        increment(state) {
            state.count++;
        },
    },
    actions: {
        increment: ({ commit }) => commit("increment"),
        incrementAsync: ({ commit, state }) => {
            return new Promise(resolve => setTimeout(() => {
                commit("increment");
                resolve(state.count)
            }, 1000))
        }
    }
})
./src/components/Counter.vue
<template>
  <div>
    <p>Count: {{ $store.state.count }} !</p>
    <button @click="increment">increment</button>
    <button @click="incrementAsync">incrementAsync</button>
  </div>
</template>

<script>
import { mapActions } from  "vuex"

export default {
  methods: {
    ...mapActions([
      "increment",
      "incrementAsync",
    ])
  }
};
</script>

Screen Shot 2019-07-07 at 6.26.07.png

Vuexを使ったカウンター作成

ここまでのを組み合わせたカウンターを作成します。

./src/store.js
import Vue from "vue"
import Vuex from "vuex"

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count: 0
  },
  getters: {
    parity: state => state.count % 2 === 0 ? "even" : "odd"
  },
  mutations: {
    increment(state) {
      state.count++;
    },
    decrement(state) {
      state.count--;
    }
  },
  actions: {
    increment: ({ commit }) => commit("increment"),
    decrement: ({ commit }) => commit("decrement"),
    incrementIfOdd: ({ commit, getters }) => getters.parity === "odd" ? commit("increment") : false,
    incrementAsync: ({ commit }) => {
      setTimeout(() => { commit("increment") }, 1000);
    }
  }
});
./src/components/Counter.vue
<template>
  <div>
    <p>Clicked {{ count }} times! Count is {{ parity }}.</p>

    <button @click="increment">Increment</button>
    <button @click="decrement">Decrement</button>
    <button @click="incrementIfOdd">Increment if Odd</button>
    <button @click="incrementAsync">Increment Async</button>
  </div>
</template>

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

export default {
  name: "Counter",
  computed: {
    ...mapState(["count"]),
    ...mapGetters(["parity"])
  },
  methods: mapActions([
    "increment",
    "decrement",
    "incrementIfOdd",
    "incrementAsync"
  ])
};
</script>

Screen Shot 2019-07-07 at 6.36.42.png


最後まで読んでいただいてありがとうございました。m(_ _)m

okumurakengo
人が作ってくれたご飯食べるときに何も言わずに食べるのは、ちょっとダメらしいという話を聞いたことがあるので、「あ、うめ、あ、うめ」って言いながら食ってたら、すごい変な人と思われてしまってしまった/初心者です、あまりわかっていません
https://bokete.jp/user/okumurakengo
qiitadon
Qiitadon(β)から生まれた Qiita ユーザー・コミュニティです。
https://qiitadon.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした