サイトトップ

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

HTML5テクニカルノート

Vue.js + Vuex入門 02: クラスバインディングと項目の削除


Vue.js + Vuex入門 01: Storeを使う」では、項目を追加してリストに表示できるようにしました。今回は、ふたつ機能を加えます。ひとつは、チェック済み項目のスタイルを変えること。もうひとつは、項目ごとに削除ができるようにすることです。

01 クラスをバインディングする

データに応じてclass属性の定めを変えるのが「クラスバインディング」です。まず、項目が済んだかどうかのデータ(completed)を、チェックボックス(<input type="checkbox"要素)の値にバインディングします(:valueディレクティブ)。その逆に、チェックボックスが切り替えられたとき(@inputイベント)は、メソッドからmutations(done())の呼び出しによりデータを変更するのです。

このとき、mutationsのメソッドには、切り替える項目のオブジェクト(todo)と新たな値(completed)を送らなければなりません。ところが、mutationsのメソッドが受け取るのは、第2引数までです。そのため、複数の値を渡したいときは、第2引数(「ペイロード」と呼ばれます)はオフジェクトのかたちにしてください(「追加の引数を渡してコミットする」参照)。なお、src/store.jsモジュールに定めたつぎのmutationsのメソッド(done())は、第2引数でオブジェクトの分割代入によりふたつの値を取り出しています。

src/components/TodoItem.vue

<template>
	<div class="view">
		<input
			type="checkbox" class="toggle"
			:value="todo.completed"
			@input="onInput"
	>

	</div>
</template>

<script>
export default {

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

src/store.js

export default new Vuex.Store({

	mutations: {

		done(state, {todo, completed}) {
			todo.completed = completed;
		}
	}
});

クラスバインディングは、ディレクティブ:classにオブジェクトのかたちでCSSのクラス(completed)を与えます(「クラスとスタイルのバインディング」参照)。もっとも、スタイルを加えたい要素(<li>)には、class属性がすでに含まれていました。重複してよいのでしょうか。結論からいえば、構いません。クラスバインディングはclass属性を上書きしないからです(「クラスが複数与えられた場合」参照)。

src/components/TodoList.vue

<li
	v-for="todo in filteredTodos"
	class="todo"
	:class="{completed: todo.completed}"

>

</li>

とはいえ、ひとつにまとめた方がわかりやすいでしょう。このようなときに用いるのが「配列構文」です。つねに与えてよいクラスは、配列に文字列の要素として加えます[*01]。データにバインディングするクラスは、オブジェクトのかたちで要素にしてください。これで、処理済みのチェックをした項目は、スタイルが変わります(図001)。

src/components/TodoList.vue

<template>
	<section class="main" v-show="todos.length" v-cloak>
		<input class="toggle-all" type="checkbox">
		<ul class="todo-list">
			<li
				v-for="todo in filteredTodos"
				:class="['todo', {completed: todo.completed}]"

			>

			</li>
		</ul>
	</section>
</template>

[*01] ただ、index.cssには該当するクラス(todo)は定められていないようです。したがって、このclass属性は除いてしまっても構いません。

図001■チェックした項目のスタイルが変わる

図001

02 項目ごとにボタンで削除する

項目のテンプレートには、すでに削除のための<button>要素が加えてあります。それぞれの項目にロールオーバーすると、CSSで右端に[×]ボタンが表れるはずです(図002)。

図002■項目にロールオーバーすると表れる削除の[×]ボタン

図002

コンポーネントsrc/components/TodoItem.vueに、ボタンクリックで呼び出すメソッド(removeTodo())を新たに定めます。モジュールsrc/store.jsmutationsに加えたメソッド(removeTodo())にcommit()を送り、第2引数に渡すのは削除すべき項目オブジェクト(todo)の参照です。

src/components/TodoItem.vue

<template>
	<div class="view">

		<button

			@click="removeTodo">
		</button>
	</div>
</template>

<script>
export default {

	methods: {
		removeTodo() {
			this.$store.commit('removeTodo', this.todo);
		},

	}
};
</script>

src/store.js

export default new Vuex.Store({

	mutations: {

		removeTodo(state, todo) {
			state.todos = state.todos.filter((item) => item !== todo);
		},

	}
});

これで、処理済み項目へのクラスバインディングと項目ごとの削除ができるようになりました。手を加えた4つのモジュールのの中身は、つぎのコード001に掲げます。実際にコードを確かめたい方は、CodeSandboxに公開した以下のサンプル001をお試しください。

コード001■クラスバインディングと項目ごとの削除

src/store.js

import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
	state: {
		todos: [],
		uid: 0
	},
	getters: {
		filteredTodos: (state) => state.todos
	},
	mutations: {
		addTodo(state, todoTitle) {
			const newTodo = todoTitle && todoTitle.trim();
			if (!newTodo) {
				return;
			}
			state.todos.push({
				id: state.uid++,
				title: newTodo,
				completed: false
			});
		},
		removeTodo(state, todo) {
			state.todos = state.todos.filter((item) => item !== todo);
		},
		done(state, {todo, completed}) {
			todo.completed = completed;
		}
	}
});

src/components/TodoList.vue

<template>
	<section class="main" v-show="todos.length" v-cloak>
		<input class="toggle-all" type="checkbox">
		<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;
		}
	}
};
</script>
<style scoped>
[v-cloak] {
	display: none;
}
</style>

src/components/TodoItem.vue

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

<script>
export default {
	name: 'TodoItem',
	props: {
		todo: Object
	},
	methods: {
		removeTodo() {
			this.$store.commit('removeTodo', this.todo);
		},
		onInput() {
			this.$store.commit('done', {
				todo: this.todo,
				completed: !this.todo.completed
			});
		}
	}
};
</script>

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

Vue.js + Vuex入門


作成者: 野中文雄
作成日: 2019年09月24日


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