LoginSignup
10
9

More than 3 years have passed since last update.

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

Posted at

以下のページが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

10
9
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
10
9