VuexでTodoリストを作成する。
【所要時間】
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を使えばいいというものではないというのも読んだので、その辺は考えて行く必要があるのだと思われる。