サイトトップ

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

HTML5テクニカルノート

Vue.js + Vuex入門 05: チェックをまとめてオン/オフする


単一ファイルコンポーネントにVuexのStoreを加えてつくるTodoMVCアプリケーションのチュートリアルシリーズ「Vue.js + Vuex入門」の第5回で加えるのは、リスト項目チェックをまとめてオン/オフできる機能です。そしてもうひとつ、Storeのデータを算出プロパティに定めるためのヘルパー関数もご紹介します。

01 全項目のチェック切り替えサインを表示する

まず、リスト項目の処理済みチェックを、まとめてオン/オフできるようにしましょう。リスト表示のコンポーネント(src/components/TodoList.vue)には、先頭にまだ使っていないチェックボックスの<input type="checkbox">要素がすでに加えてありました。そして、<input type="checkbox">要素には、id属性が与えられています。対応する<label>要素を加えると、index.cssによりチェックサインが表れるはずです(図001参照)。

このチェックボックス(<input type="checkbox">)をモジュールsrc/store.jsgettersに新たに定めるメソッド(allDone)と、つぎのようにデータバインディングします。戻り値は、すべての項目が処理済みだとtrueとなるブール(論理)値です。checked属性にもバインディングするのを忘れないでください。

src/components/TodoList.vue

<template>
	<section class="main" v-show="todos.length" v-cloak>
		<input
			id="toggle-all"
			class="toggle-all"
			type="checkbox"
			:value="allDone"
			:checked="allDone"
		>
		<label for="toggle-all" />

	</section>
</template>


<script>

export default {

	computed: {

		allDone() {
				return this.$store.getters.allDone;
		}
	}
};
</script>

src/store.js

export default new Vuex.Store({

	getters: {

		allDone: (state, getters) => getters.remaining === 0
	},

});

チェックサインのオン/オフを切り替えたときの処理は、このあと加えます。けれど、スタイル(クラスtoggle-all)が与えてあるので(index.css参照)、すべての項目をチェックすると、checked属性にバインディングした値(allDone)はtrueになり、アクティブな表示に変わります。

図001■全項目を切り替えるチェックサイン

図001

02 全項目のチェックをまとめて切り替える

前項ですべての項目を処理済みにチェックしたとき、チェックボックス(<input type="checkbox">)の値(value)と要素のスタイルが変わるようにしました。本項では、チェックボックスのオン/オフで、全項目のチェックをまとめて切り替えるようにしましょう。そのためには、コンポーネントsrc/components/TodoList.vueの算出プロパティ(allDone)の値を変えなければなりません。算出プロパティは、デフォルトでは読み取り専用です。けれど、Setter関数が加えられます(「算出Setter関数」参照)。

算出プロパティ(allDone)が返すのは、モジュールsrc/store.jsに定めたgettersの同名メソッドへの参照です。そこで、mutationsに加える新たなメソッド(setAllDone())で、つぎのように戻り値を変えます。算出Setter関数を呼び出すのは、コンポーネントsrc/components/TodoList.vueのチェックボックス(<input type="checkbox">)のchangeイベントに加えるリスナーメソッド(onInput())です。処理の済んでいない項目があればすべてを処理済みにし、全項目処理が終わっていたら逆にすべて未処理に戻します。

src/components/TodoList.vue

<template>
	<section class="main" v-show="todos.length" v-cloak>
		<input

			@change="onInput"
		>

	</section>
</template>


<script>

export default {

	computed: {

		// allDone() {
		allDone: {
			get() {
				return this.$store.getters.allDone;
			},
			set(value) {
				this.$store.commit('setAllDone', value);
			}
		}
	},
	methods: {
		onInput() {
			this.allDone = !this.allDone;
		}
	}
};
</script>

src/store.js

export default new Vuex.Store({

	mutations: {

		setAllDone(state, value) {
			state.todos.forEach((todo) =>
				todo.completed = value
			);
		}
	}
});

これですべての項目のチェックを切り替えるチェックサインができ上がりました(図002)。コンポーネントsrc/components/TodoList.vueとモジュールsrc/store.jsの記述は、以下のコード001にまとめたとおりです。

図002■全項目のチェックが変えられる

図002

コード001■全項目のチェック切り替えサインが加わったリスト表示コンポーネントとStoreモジュール

src/components/TodoList.vue

<template>
	<section class="main" v-show="todos.length" v-cloak>
		<input
			id="toggle-all"
			class="toggle-all"
			type="checkbox"
			:value="allDone"
			:checked="allDone"
			@change="onInput"
		>
		<label for="toggle-all" />
		<ul class="todo-list">
			<li
				v-for="todo in filteredTodos"
				:class="['todo', {completed: todo.completed}]"
				:key="todo.id"
			>
				<todo-item
					:todo="todo" />
			</li>
		</ul>
	</section>
</template>

<script>
import TodoItem from './TodoItem.vue';
export default {
	name: 'TodoList',
	components: {
		TodoItem
	},
	computed: {
		todos() {
			return this.$store.state.todos;
		},
		filteredTodos() {
			return this.$store.getters.filteredTodos;
		},
		allDone: {
			get() {
				return this.$store.getters.allDone;
			},
			set(value) {
				this.$store.commit('setAllDone', value);
			}
		}
	},
	methods: {
		onInput() {
			this.allDone = !this.allDone;
		}
	}
};
</script>
<style scoped>
[v-cloak] {
	display: none;
}
</style>

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
			);
		}
	}
});

03 mapState()ヘルパー関数を使う

つぎに、Vuexのヘルパー関数をご紹介しましょう。mapState()関数は戻り値が、算出プロパティに定めるメソッドを収めたオブジェクトです。引数には算出プロパティとするメソッドを渡します(「mapStateヘルパー」参照)。ただし、引数にStoreのstateを受け取るので、this.$store.stateの参照が要りません。もうひとつ注意しなければならないのは、オブジェクトが返されるため、他にも算出プロパティがあるときは、メソッドを取り出して展開しなければならないことです。そのため、つぎのようにスプレッド構文...を用います(「スプレッド構文を使う」参照)。

src/components/TodoController.vue

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

	computed: {
		/* todos() {
			return this.$store.state.todos;
		}, */
		...mapState({
			todos: (state) => state.todos,
			visibility: (state) => state.visibility
		}),

		/* visibility() {
			return this.$store.state.visibility;
		} */
	}
}

04 mapGetters()ヘルパー関数を使う

mapGetters()ヘルパー関数が返すのも、算出プロパティに定めるメソッドを収めたオブジェクトです(「mapGettersヘルパー」参照)。ただし、Storeのgettersの参照をそのまま返します。そのため、引数は配列で、要素は参照するgettersの名前の文字列です。算出プロパティ名を変えたいときは、引数はオブジェクトにして、新しい名前のプロパティにgettersの文字列を値にすることもできます。今回は、gettersの参照をそのまま返せばよいので、引数はつぎのように配列にしました。やはりメソッドは展開しなければならないので、スプレッド構文...を忘れないでください。

src/components/TodoController.vue

import {mapState, mapGetters} from 'vuex';

export default {

	computed: {

		/* remaining() {
			return this.$store.getters.remaining;
		},
		filters() {
			return this.$store.getters.filters;
		}, */
		...mapGetters([
			'remaining',
			'filters'
		])
	}
}

05 mapState()関数の簡易な構文

mapState()関数はstateの参照から取り出した値をさまざまに加工できます。けれども、コンポーネントsrc/components/TodoController.vueでは、値の参照をただ返しているだけです。こういうときは、mapGetters()の構文と同じく、引数にプロパティ名の文字列を要素にした配列が渡せます。

src/components/TodoController.vue

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

	computed: {
		/* ...mapState({
			todos: (state) => state.todos,
			visibility: (state) => state.visibility
		}), */
		...mapState([
			'todos',
			'visibility'
		]),

	}
}

これでコンポーネントsrc/components/TodoController.vueもでき上がりです(コード002)。CodeSandboxに公開した以下のサンプル001で、各モジュールのコードと動きがお確かめいただけます。

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

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>
	</footer>
</template>

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

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

Vue.js + Vuex入門


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


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