次のようなTodoリストを作ります
※Open Snadboxでコードも確認できます。(結構適当です)
ページの構成
一覧ページ、追加ページ、編集ページで構成しており、vue-routerで次のように設定しています。
import Vue from "vue"; import Router from "vue-router"; import Index from "./components/pages/index"; import Edit from "./components/pages/edit"; import Add from "./components/pages/add"; Vue.use(Router); export default new Router({ mode: "history", routes: [ { path: "/", component: Index }, { path: "/add", component: Add }, { path: "/edit/:id", component: Edit } ] });
vue-routerを知らないという方はこちらの記事を参考にしてください。

データの状態管理
さてここからが本題なわけですが、このようなページ構成の場合どのようにしてデータを管理すればよいのでしょうか。結論から言うと、次のようなイメージでTodoリストのデータを管理します。
Vueインスタンス内でデータを一元管理し、どのコンポーネントからもアクセス可能になるようにします。コンポーネントは各々でデータを保持していますが、自身が消えるタイミングで当然データも消えてしまいます。今回はVueインスタンス内で管理しているため、Vueインスタンスが消えるまではデータは状態を保持し続けます。
結局のところ何がしたいのかというと、いろんなコンポーネントで使うデータは1つにまとめて使えればみんなハッピーだよね、ということです。これを実現するのがVuexになります。
Vuexとは何か
先ほど説明した通り、Vuexはデータを一元管理することが基本的な役割です。ただし、ちょっとしたルールがあります。
Vuexは、ステート(state)、ゲッター(getters)、ミューテーション(mutations)、アクション(actions)に役割が分かれます。これらをルールに従って使用することでデータを一元管理できるようになります。
それぞれの説明の前に、まずはVuexを使える状態にしましょう。
Vuexの設定
まずはVuexをインストールします。
$npm install --save vuex $yarn add vuex
次にsrcディレクトリ直下に「store」というディレクトリを作成し、「index.js」を次の内容で作成してください。
import Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); export default new Vuex.Store({ store: {}, getters: {}, mutations: {}, actions: {} });
見てわかるようにVue.storesの引数として先ほどの4つの役割を定義していきます。
storeディレクトリは作成する必要はありませんが、Vuex用のディレクトリとしてよく利用されます。
あとは「main.js」で今作成した内容を読み込ませます。
import Vue from "vue"; import App from "./App.vue"; import router from "./router"; import store from "./store"; //これを追加 Vue.config.productionTip = false; new Vue({ store, //これを追加 router, render: h => h(App) }).$mount("#app")
これで準備は完了です。ではそれぞれの役割について説明していきます。
ステート(state)
ステートはデータそのものを表します。今回はTodoのデータとTodoのid付けのためのsequenceを定義します。
state: { todos: [], sequence: 1 }
ステートは「$store.state」によって次のようにして参照します。
this.$store.state.todos
ゲッター(getters)
ゲッターはcomputedとよく似ており、ステートを加工した値を取得する際に使用します。今回は、タスクが完了したリスト、タスクが完了していないリスト、それぞれの状態の数、IDに該当するタスクの取得を定義しています。
getters: { doneTodos: state => { return state.todos.filter(todo => todo.done); }, notDoneTodos: state => { return state.todos.filter(todo => !todo.done); }, todosCount: state => { return state.todos.length; }, doneTodosCount: (state, getters) => { return getters.doneTodos.length; }, notDoneTodosCount: (state, getters) => { return getters.notDoneTodos.length; }, getTodoById: state => id => { return state.todos.find(todo => todo.id === id); } },
gettersは必ず引数にstateを持ちます。自身のメソッドを使用したい場合は、引数にgettersを追加します。getToByIdのように別途引数を設定することもできます。
gettersを参照する場合は次のようにします。
this.$store.getters.doneTodos
mapGetters
mapGettersを使用することでgettersの内容をコンポーネントのcomputedにマッピングすることができます。
import { mapGetters } from "vuex" export default { computed: { ...mapGetters([ 'doneTodos', 'notDoneTodos' ]) } }
こうすることで次のように定義したことと同義になります。
export default { computed: { doneTodos() { return this.$store.getters.doneTodos; }, notDoneTodos() { return this.$store.getters.notDoneTodos; } } }
ミューテーション(mutation)
ミューテーションは、ステートの値を更新するために使用します。今回は、Todoの追加、Todoの更新、Todoの削除などを定義します。引数を必要とする場合は第二引数に設定しますが、複数存在する場合はオブジェクトとして設定します。
mutations: { setTodos(state, todos) { state.todos = todos; }, setSequence(state, sequence) { state.sequence = sequence; }, create(state, { title, detail }) { const todo = { id: state.sequence, title: title, detail: detail, done: false }; state.todos.push(todo); state.sequence++; }, update(state, { id, title, detail }) { const index = state.todos.findIndex(todo => todo.id === id); if (index >= 0) { state.todos[index].title = title; state.todos[index].detail = detail; } }, delete(state, id) { const index = state.todos.findIndex(todo => todo.id === id); if (index >= 0) { state.todos.splice(index, 1); } }, changeDone(state, id) { const index = state.todos.findIndex(todo => todo.id === id); if (index >= 0) { state.todos[index].done = !state.todos[index].done; } } }
ミューテーションは基本的にはコンポーネントから呼ばないようにします。ミューテーションはアクションからのみ呼び出します。
アクション(actions)
アクションはコンポーネントから直接呼び出され、非同期に外部APIとのデータのやり取りを行います。またミューテーションを使用してデータの設定を行います。今回は外部APIとのやり取りはないのでデータの設定のみ行います。
actions: { readTodos({ commit }) { //外部から取得したという想定 let todos = [ { id: 1, title: "Vuexの記事を書く", detail: "頑張って書きます", done: false }, { id: 2, title: "Nuxtの記事を書く", detail: "頑張って書きますよ", done: true } ]; let sequence = 3; commit("setTodos", todos); commit("setSequence", sequence); }, createTodo({ commit }, todo) { commit("create", todo); }, updateTodo({ commit }, todo) { commit("update", todo); }, deleteTodo({ commit }, id) { commit("delete", id); }, changeDone({ commit }, id) { commit("changeDone", id); } }
ミューテーションを用いたデータの設定は、引数にあるcommitを用いて行います。
アクションは次のようにdispatchによって呼び出します。
this.$store.dispatch('readTodos')
mapActions
mapActionsは、mapGettersと同じようにコンポーネントのmethodsにマッピングすることができます。
export default { methods: { ...mapActions(["changeDone", "deleteTodo"]) } }
こうすることで次と同義になります。
export default { methods: { changeDone(id) { this.$store.dispatch('changeDone', id); }, deleteTodo(id) { this.$store.dispatch('deleteTodo', id); } } }
Vuexまとめ
Vuexのデータはステートで定義され、コンポーネントはステートを直接参照するかゲッターによって参照します。ステートの更新は、アクションを実行し、アクション内のミューテーションによって更新されます。
部品は整ったので後は各コンポーネントで呼び出すだけです。残りのコードはCode Sandboxを参考にしてください。(コードは結構やっつけです。すみません。)
Vuexは必ずしも使わないというわけではありません。今回のような場合もVuexを使用せずとも実装はできます。Vuexを使用する場合は本当に必要かどうかよく考えるのが必要かもしれません。
- Vue.jsのおすすめ書籍はコチラ -
コメント