サイトトップ

Director Flash 書籍 業務内容 プロフィール

HTML5テクニカルノート

Vue.js + Vuex入門 06: チェックした項目をまとめて削除する


単一ファイルコンポーネントにVuexのStoreを加えてつくるTodoMVCアプリケーションのチュートリアルシリーズ「Vue.js + Vuex入門」の第6回で新たに加えるのは、チェック済みの項目をまとめてリストデータから除くボタンです。さらに、Storeのmutationsに送るcommit()methodsに定める、ヘルパー関数mapMutations()も使ってみます。

01 チェック済みの項目をすべて削除する

新しい削除ボタンは、フッタのコンポーネントsrc/components/TodoController.vueにぎのように加えましょう。clickイベントのリスナーメソッド(removeCompleted())からcommit()で呼び出すのは、モジュールsrc/store.jsmutationsに新たに定めた同名のメソッド(removeCompleted())です。未処理項目のフィルタメソッド(filters.active())で取り出した項目データを、もとのリストデータ(todos)に上書きしています。

src/components/TodoController.vue

<template>
	<footer class="footer" v-show="todos.length" v-cloak>

		<button class="clear-completed"
			v-show="todos.length > remaining"
			@click="removeCompleted">
			Clear completed
		</button>
	</footer>
</template>


<script>

export default {

	methods: {
		removeCompleted() {
			this.$store.commit('removeCompleted');
		}
	}
};
</script>

src/store.js

export default new Vuex.Store({

	mutations: {

		removeCompleted(state) {
			state.todos = filters.active(state.todos);
		}
	}
});

これで、ボタンクリックにより、リストデータから処理済み項目がすべて除かれます(図001)。モジュールsrc/store.jsは、これ以上書き替えしません。全体の記述は、以下のコード001にまとめたとおりです。

図001■ボタンで処理済み項目をすべて除く

図001

コード001■チェック済み項目をデータから除く

src/store.js

import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const STORAGE_KEY = 'todos-vuejs-2.6';
const todoStorage = {
	fetch() {
		const todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
		todos.forEach(function(todo, index) {
			todo.id = index;
		});
		todoStorage.uid = todos.length;
		return todos;
	},
	save(todos) {
		localStorage.setItem(STORAGE_KEY, JSON.stringify(todos));
	}
};
const filters = {
	all(todos) {
		return todos;
	},
	active(todos) {
		return todos.filter((todo) =>
			!todo.completed
		);
	},
	completed(todos) {
		return todos.filter((todo) =>
			todo.completed
		);
	}
};
export default new Vuex.Store({
	state: {
		todos: todoStorage.fetch(),
		visibility: 'all'
	},
	getters: {
		filteredTodos: (state) =>
			filters[state.visibility](state.todos),
		remaining: (state) => {
			const todos = state.todos.filter((todo) => !todo.completed);
			return todos.length;
		},
		filters: (state) => filters,
		allDone: (state, getters) => getters.remaining === 0
	},
	mutations: {
		addTodo(state, todoTitle) {
			const newTodo = todoTitle && todoTitle.trim();
			if (!newTodo) {
				return;
			}
			state.todos.push({
				id: todoStorage.uid++,
				title: newTodo,
				completed: false
			});
		},
		removeTodo(state, todo) {
			state.todos = state.todos.filter((item) => item !== todo);
		},
		done(state, {todo, completed}) {
			state.todos = state.todos.map((item) => {
				if(item === todo) {
					item.completed = completed
				}
				return item;
			});
		},
		save(state) {
			todoStorage.save(state.todos);
		},
		hashChange(state) {
			const visibility = window.location.hash.replace(/#\/?/, '');
			if (filters[visibility]) {
				state.visibility = visibility;
			}
		},
		setAllDone(state, value) {
			state.todos.forEach((todo) =>
				todo.completed = value
			);
		},
		removeCompleted(state) {
			state.todos = filters.active(state.todos);
		}
	}
});

02 ミューテーションへのコミットをまとめるヘルパー関数mapMutations()

Storeのmutationsに送るcommit()をコンポーネントのmethodsオプションに定めるヘルパー関数がmapMutations()です。commit()メソッドをコンポーネントの$storeから参照しなくて済むようになります(「コンポーネント内におけるミューテーションのコミット」参照)。

コンポーネントsrc/components/TodoController.vuemethodsに加わるメソッドはひとつ(removeCompleted())で、mutationsに定められた同名のメソッドに引数もなくcommit()を送っているだけです。この場合は、以下のようなmapMutations()の簡易な構文が使えます。

関数の引数はオブジェクトで、プロパティがメソッドとして扱われ、値はミューテーションにコミットする文字列のメソッド名です。返されるオブジェクトが、コンポーネントのmethodsオプションに渡すオブジェクトになります。コンポーネントのメソッドはひとつなので、スプレッド構文...による展開はしません(「Vue.js + Vuex入門 05」03「mapState()ヘルパー関数を使う」参照)。

src/components/TodoController.vue

import {mapState, mapGetters, mapMutations} from 'vuex';
export default {

	methods: /* {
		removeCompleted() {
			this.$store.commit('removeCompleted');
		}
	} */
		mapMutations({
			removeCompleted: 'removeCompleted'
		})
}

コンポーネントsrc/components/TodoController.vuemethodsの定めを、mapMutations()で書き替えました。メソッドがひとつなので、あまりありがたみが感じられないかもしれません。けれど、数が増えてきたとき、ヘルパー関数を使うことですっきりとまとめられるでしょう。コンポーネントの記述全体は、つぎのコード002のとおりです。

コード002■フッタのコンポーネントにヘルパー関数mapMutations()を使う

src/components/TodoController.vue

<template>
	<footer class="footer" v-show="todos.length" v-cloak>
		<span class="todo-count">
			<strong>{{remaining}}</strong> {{remaining | pluralize}} left
		</span>
		<ul class="filters">
			<li v-for="(value, key) in filters" :key="key">
				<a
					:href="'#/' + key"
					:class="{selected: visibility === key}"
				>
					{{ key[0].toUpperCase() + key.substr(1) }}
				</a>
			</li>
		</ul>
		<button class="clear-completed"
			v-show="todos.length > remaining"
			@click="removeCompleted">
			Clear completed
		</button>
	</footer>
</template>

<script>
import {mapState, mapGetters, mapMutations} from 'vuex';
export default {
	name: 'TodoController',
	filters: {
		pluralize(n) {
			return n === 1 ? 'item' : 'items';
		}
	},
	computed: {
		...mapState([
			'todos',
			'visibility'
		]),
		...mapGetters([
			'remaining',
			'filters'
		])
	},
	methods: mapMutations({
		removeCompleted: 'removeCompleted'
	})
};
</script>

03 mapMutations()ヘルパー関数の標準構文

コンポーネントsrc/components/TodoItem.vuemethodsに定められたメソッドは、ミューテーションに送るコミットに、引数を添えなければなりません。このような場合は、mapMutations()の標準的な構文を用いるのが原則です。引数のオブジェクトには、メソッドを収めます。この引数に渡されるのがcommit()メソッドです。したがって、methodsオプションの定めは、つぎのように書き替えられます。いちいち$storeを参照しなくて済む分、少しすっきりしました。

src/components/TodoItem.vue

import { mapMutations } from 'vuex';
export default {

	methods: /* {
		removeTodo() {
			this.$store.commit('removeTodo', this.todo);
		},
		onInput() {
			this.$store.commit('done', {
				todo: this.todo,
				completed: !this.todo.completed
			});
		}
	} */
		mapMutations({
			removeTodo(commit) {
				commit('removeTodo', this.todo);
			},
			onInput(commit) {
				commit('done', {
					todo: this.todo,
					completed: !this.todo.completed
				});
			}
		})
}

04 mapMutations()ヘルパー関数の簡易構文に書き替えられる場合

前掲リスト項目のコンポーネントsrc/components/TodoItem.vueの場合は、実はmapMutations()ヘルパー関数の簡易構文に書き替えることもできます。引数をテンプレートのv-on(@)ディレクティブから渡すことができるからです。こちらの方が、よりすっきりするかもしれません。

src/components/TodoItem.vue

<template>
	<div class="view">
		<input

			@input="onInput({todo: todo, completed: !todo.completed})"
		>
			<!-- @input="onInput" -->

		<button

			@click="removeTodo(todo)">
			<!-- @click="removeTodo"> -->
		</button>
	</div>
</template>

<script>

export default {

	methods: mapMutations({
		/* removeTodo(commit) {
			commit('removeTodo', this.todo);
		},
		onInput(commit) {
			commit('done', {
				todo: this.todo,
				completed: !this.todo.completed
			});
		} */
		removeTodo: 'removeTodo',
		onInput: 'done'
	})
};
</script>

リスト項目のコンポーネントsrc/components/TodoItem.vuemethodsにはmapMutations()の簡易構文を用いて、記述をつぎのコード003にまとめました。

コード003■リスト項目のコンポーネントにヘルパー関数mapMutations()を使う

src/components/TodoItem.vue

<template>
	<div class="view">
		<input
			type="checkbox" class="toggle"
			:value="todo.completed"
			:checked="todo.completed"
			@input="onInput({todo: todo, completed: !todo.completed})"
		>
			<label>{{todo.title}}</label>
		<button
			class="destroy"
			@click="removeTodo(todo)">
		</button>
	</div>
</template>

<script>
import { mapMutations } from 'vuex';
export default {
	name: 'TodoItem',
	props: {
		todo: Object
	},
	methods: mapMutations({
		removeTodo: 'removeTodo',
		onInput: 'done'
	})
};
</script>

05 mapMutations()ヘルパー関数の標準構文を使わなければならない場合

項目入力のコンポーネントsrc/components/TodoInput.vueにはmethodsにメソッドがひとつ(addTodo())定められ、同名のミューテーションにコミットと引数を送っています。ここでは、mapMutations()の標準構文を使うしかありません。処理がcommit()を呼び出すだけでけではないからです。

src/components/TodoInput.vue

<script>
import { mapMutations } from 'vuex';
export default {

	methods: /* {
		addTodo() {
			this.$store.commit('addTodo', this.newTodo);
			this.newTodo = '';
		}
	} */
		mapMutations({
			addTodo(commit) {
				commit('addTodo', this.newTodo);
				this.newTodo = '';
			}
		})
};
</script>

項目入力のコンポーネントsrc/components/TodoInput.vueの記述はつぎのコード002のとおりです。これでモジュールの書き替えはすべて済みました。各モジュールのコードと動きは、CodeSandboxに公開した以下のサンプル001でお確かめください。

コード004■項目入力のコンポーネントにヘルパー関数mapMutations()を使う

src/components/TodoInput.vue

<template>
	<input
		v-model="newTodo"
		class="new-todo" autofocus autocomplete="off"
		placeholder="What needs to be done?"
		@keypress.enter="addTodo"
	>
</template>

<script>
import { mapMutations } from 'vuex';
export default {
	name: 'TodoInput',
	data() {
		return {
			newTodo: ''
		};
	},
	methods: mapMutations({
		addTodo(commit) {
			commit('addTodo', this.newTodo);
			this.newTodo = '';
		}
	})
};
</script>

サンプル001■vue-vuex-todo-mvc-06

Vue.js + Vuex入門


作成者: 野中文雄
作成日: 2019年10月26日


Copyright © 2001-2019 Fumio Nonaka.  All rights reserved.