VuexでTodoリストを作成する。

Tatsuya Asami
11 min readMar 10, 2019

【所要時間】

1時間半くらい(2019年3月10日)

【概要】

前回作ったTodoリストをVuexを使って作り変える。

今回のレポジトリ(vuex1ブランチ)

【要約・学んだこと】

まずはVuexの中身

src/store.js

import Vue from "vue";
import Vuex from "vuex";
import moduleTodo from "@/components/moduleTodo";
Vue.use(Vuex);export default new Vuex.Store({
modules: {
todo: moduleTodo
}
});

src/components/moduleTodo.js

const moduleTodo = {
namespaced: true,
state: {
todos: [
{ id: 1, text: "vue-router", done: false },
{ id: 2, text: "vuex", done: false },
{ id: 3, text: "vue-loader", done: false },
{ id: 4, text: "awsome-vue", done: false },
{ id: 5, text: "vue-router", done: true }
],
newTodo: "",
showTodo: "all"
},
mutations: {
input: function(state, child) {
state.newTodo = child;
},
addTodo: function(state) {
let text = state.newTodo && state.newTodo.trim();
if (!text) {
return;
}
const id = state.todos.slice(-1)[0].id + 1;
state.todos.push({
id: id,
text: text,
done: false
});
state.newTodo = "";
},
removeTodo: function(state) {
for (let i = state.todos.length - 1; i >= 0; i--) {
if (state.todos[i].done) state.todos.splice(i, 1);
}
},
changeShowTodo(state, e) {
state.showTodo = e;
}
},
actions: {}
};
export default moduleTodo;

moduleTodo.jsには元々Hello.jsにあったstateとmethodsをほぼそのまま移管。変更した点といえば、this.newTodo, this.todosで呼び出していたデータを、state.newTodos, state.showTodoと変更した。

namescaped = trueがないと、各コンポーネントからの呼び出しが面倒になりそうなので、基本的につけるようにしようと思う。

今回はmodulesを宣言する必要はなかったが、実際に使用する場合はほぼ使うと思うので、使用するコンポーネントのディレクトリ内にmoduleTodo.jsを作成。vuexのルートとなるstore.jsは元々あったsrc/store.jsのままにした。

普通はどうするかわからないが、大規模になったら同様に各ディレクトリ内でmoduleを取り扱うjsファイルを作成すればわかりやすいはず。

src/components/ToggleArea.vue

<template>
<div>
<div class="toggle-area">
<button @click="changeShowTodo('all')">All</button>
<button
@click="changeShowTodo('inProgress')">In progress</button>
<button
@click="changeShowTodo('done')">Done</button>
</div>
</div>
</template>
<script>
import { mapMutations } from "vuex";
export default {
name: "ToggleArea",
methods: {
...mapMutations("todo", ["changeShowTodo"])
}
};
</script>

…mapMutationsでvuexからmutationsを呼び出す。スプレッドを書くことで、ローカルのmethodsと共存出来るので、常に… はつけるようにする。

v-on:clickの書き方でちょっとハマった。

元々は

<button @click="$emit('changeShowTodo', 'all')">All</button>

と書いていたが、$emitの場合はmethodsを()でくくり、メソッド名と引数の間に, を入れる。

同じコンポーネント内にあるmethodsは下記のように普通に書けば良い。

<button @click="changeShowTodo('all')">All</button>

src/components/InputArea.vue

<template>
<div class="input-area">
<button @click="addTodo">ADD TASK</button>
<button
@click="removeTodo">DELETE FINISHED TASKS</button>
<p>
input:
<input v-model="inputValue" type="text" />
</p>
<p>task:{{ inputValue }}</p>
</div>
</template>
<script>
import { createNamespacedHelpers } from "vuex";
const { mapState, mapMutations } = createNamespacedHelpers("todo");
export default {
name: "InputArea",
computed: {
...mapState(["todos", "newTodo"]),
inputValue: {
get() {
return this.newTodo;
},
set(value) {
this.input(value);
}
}
},
methods: {
...mapMutations(["input", "addTodo", "removeTodo"])
}
};
</script>

vuex内のstateを呼び出すにはmapStateを呼び出す。こちらもmapMutationsと同様、常にスプレッドを書くようにしておけば、ローカルで使うcomputedを作成した時も問題ない。

vuexからのimportの方法を変えてみた。

先ほどは

import { mapMutations } from "vuex";
...mapMutations("todo", ["changeShowTodo"])

と、mapMutationsの第一引数にmodule名を書いたが、

import { createNamespacedHelpers } from "vuex";
const { mapState, mapMutations } = createNamespacedHelpers("todo");
...mapState(["todos", "newTodo"]),
...mapMutations(["input", "addTodo", "removeTodo"])

今回はcreateNamespacedHelpersにmodule名を書いた。

どっちでもいいと思うが、記述量はほぼ変わりなく、もしmodule名の変更があった場合書き換えるのが1箇所で済むので、このように書いた方が無難な気がする。

src/components/TaskList.vue

<template>
<div class="task-list">
<label
v-for="todo in toggle"
:key="todo.id"
class="task-list__item"
:class="{ 'task-list__item--checked': todo.done }"
>
<input v-model="todo.done" type="checkbox" />
<input v-model="todo.editing" type="checkbox" />
<input
v-if="todo.editing"
v-model="todo.text"
@keyup.enter="todo.editing = !todo.editing"
/>
<span v-else>{{ todo.text }}</span>
</label>
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
name: "TaskList",
computed: {
...mapState("todo", ["todos", "showTodo"]),
toggle: function() {
if (this.showTodo === "done") {
return this.todos.filter(todos => todos.done === true);
} else if (this.showTodo === "inProgress") {
return this.todos.filter(todos => todos.done === false);
} else {
return this.todos;
}
}
}
};
</script>

他のファイルと同様。このコンポーネントに関してはイベントがないため、ほぼ書き換えが不要だった。

src/components/Hello.vue

<template>
<div>
{{ msg }}
<InputArea />
<ToggleArea />
<TaskList />
</div>
</template>
<script>
import InputArea from "@/components/InputArea.vue";
import TaskList from "@/components/TaskList.vue";
import ToggleArea from "@/components/ToggleArea.vue";
export default {
name: "Hello",
components: {
InputArea,
TaskList,
ToggleArea
},
data: function() {
return {
msg: "Welcome to my Todo"
};
}
};
</script>

スッキリした。

元々あったdataやmethodsはmoduleへ、また、v-bindで渡していたデータやメソッドがなくなった。

<template>
<div>
{{ msg }}
<InputArea
:new-todo="newTodo"
@input="input"
@addTodo="addTodo"
@removeTodo="removeTodo"
/>
<ToggleArea @changeShowTodo="changeShowTodo" />
<TaskList :todos="todos" :show-todo="showTodo" />
</div>
</template>

【わからなかったこと】

actions、getterとかの使いどころがいまいちわかっていない。

【感想】

React, Reduxと比べると、記述の面倒さがほぼないので、全てvuexで状態管理をした方がわかりやすい気がした。

親1つに子が3つしかいないアプリだとvuexを使う必要はないが、vuexを使うめんどくさのようなのは感じなかった。データやメソッドをtemplate上で渡す方が若干わかりにくく間違えやすい気がした。

ただどこかでやたらvuexを使えばいいというものではないというのも読んだので、その辺は考えて行く必要があるのだと思われる。

--

--