サイトトップ

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

HTML5テクニカルノート

React + Redux入門 03: 項目の処理済みと未処理でスタイルを変える


React + Redux入門 02: フィールドに入力したテキストを項目リストに加える」で、リストに項目が追加できるようになりました。Todoリストですから、項目が済んだかどうかのチェックをつけ、スタイルが変わるようにしましょう。

01 ActionとReducer

チェックボックスを使ってもよいのですが、つぎのような絵文字で示すことにします。

👋 未処理
👌 処理済み

項目が未処理か処理済みかも、もちろん状態としてStoreにもたせます。まずつくるのは、Action Creatorです。モジュールsrc/actions/index.jsに、つぎのようにtoggleTodoを定めてください。引数に受け取ったidを、Actionオブジェクトに納めて返します。

src/actions/index.js

export const toggleTodo = (id) => ({
	type: 'TOGGLE_TODO',
	id
});

このActionオブジェクトを送るのは、クリックされて処理/未処理が切り替わる項目(Todo)です。けれど、そのための関数(onClick)は、つぎのように親のモジュールsrc/components/TodoList.jsに定めて、テンプレートで子コンポーネントに与えます。

ActionオブジェクトをStoreに送るには、connect()関数の第2引数に関数(mapStateToProps())を渡さなければなりません(「Connect: Dispatching Actions with mapDispatchToProps」参照)。戻り値は、dispatch()関数を引数に受け取り、ActionオブジェクトをStoreに送る関数です。

src/components/TodoList.js

import { toggleTodo } from '../actions';

// const TodoList = ({ todos }) => (
const TodoList = ({ todos, toggleTodo }) => (
	<ul className="todo-list">
		{todos.map((todo) =>
			<Todo

				onClick={() => toggleTodo(todo.id)}
			/>
		)}
	</ul>
);

const mapDispatchToProps = (dispatch) => ({
	toggleTodo: (id) => dispatch(toggleTodo(id))
});

export default connect(
	mapStateToProps,
	mapDispatchToProps
)(TodoList);

モジュールsrc/reducers/todos.jsに加えるReducerは、つぎのとおりです。チェックが切り替わる項目idをActionから受け取って、そのデータのプロパティcompletedの論理値を反転します。

src/reducers/todos.js

const todos = (state = [], action) => {
	switch (action.type) {

		case 'TOGGLE_TODO':
			return state.map((todo) =>
				(todo.id === action.id)
					? {...todo, completed: !todo.completed}
					: todo
			)

	}
};

項目のモジュールsrc/components/Todo.jsは、つぎのように引数オブジェクトから取り出すプロパティが増えます。onClick属性に与えるのは、親コンポーネントから受け取った関数(onClick)です。処理済みかどうかのプロパティ(completed)は、加えるチェックマークとstyle属性の切り替えに使います。

src/components/Todo.js

// const Todo = ({ text }) => (
//  <li>
const Todo = ({ onClick, completed, text }) => (
	<li
		onClick={onClick}
		style={{
			textDecoration: completed ? 'line-through' : 'none'
		}}
	>
		{completed ? "👌" : "👋"}{" "}

	</li>
);

これで、リスト項目をクリックしたとき、チェックサインのオン/オフが切り替わるようになります(図001)。

図001■項目をクリックするとチェックサインが切り替わる

図001

02 要素のクラスを切り替える

ここで、src/App.cssを、つぎのコード001のように書き替えてください。

コード001■src/App.css


.todo-list {
	margin-top: 1rem;
	text-align: left;
	list-style: none;
}
.todo-item {
	font-family: monospace;
	cursor: pointer;
	line-height: 1.5;
}
.todo-item__text--completed {
	text-decoration: line-through;
	color: lightgray;
}

実は、クラスtodo-listは、すでにモジュールsrc/components/TodoList.jsのリスト(<ul>要素)に割り当ててありました。

src/components/TodoList.js

const TodoList = ({ todos, toggleTodo }) => (
	<ul className="todo-list">

	</ul>
);

項目テキストのクラスがtodo-itemです。そして、処理済み項目には、todo-item__text--completedを与えます。モジュールsrc/components/Todo.jsに、つぎのように書き加えればよいでしょう。

src/components/Todo.js

const Todo = ({ onClick, completed, text }) => (
	<li

		/* style={{
			textDecoration: completed ? 'line-through' : 'none'
		}} */
		className={'todo-item' +
			(completed ? ' todo-item__text--completed' : '')
		}
	>

	</li>
);

03 Classnamesで要素のクラスを定める

複数のクラス名を組み合わせてclassName属性に与えたいときに使えるユーティリティがClassnamesです。引数にはクラス名をいくつでも渡せます。値は文字列またはオブジェクトです。クラス名をプロパティにしたオブジェクトにすると、値がtrueのときに適用されます。npmでインストールしましょう。

src/components/Todo.js

npm install classnames

src/components/Todo.jsのテンプレートの項目テキスト(<span>要素)にclassName属性を加えたのが、つぎのコードです。Classnamesの関数(classNames())により、項目のcompletedプロパティがtrueのときに、処理済み用のクラス(todo-item__text--completed)が割り当てられます。

src/components/Todo.js

import classNames from 'classnames';

const Todo = ({ onClick, completed, text }) => (
	<li

		className={classNames(
			"todo-item",
			{"todo-item__text--completed": completed}
		)}
	>

	</li>
);

なお、React Redux公式サイトの「Basic Tutorial」でつくるTodoリストにも、少しコンポーネントの組み立ては違うものの、Classnamesが用いられています。ただし、関数の引数はつぎのような書き方です(「Todo App with Redux」参照)。論理演算子を条件判定に使っており、少しわかりにくいきらいがあります(「if文なしに論理演算子で条件判定の処理をする」)。また、前項02のように条件演算子?:で書くのと、さほどかわりばえしません。上記のようにオブジェクトで値に条件を与える方がわかりやすいでしょう。


className={classNames(
  "todo-item",
  todo && todo.completed && "todo-item__text--completed"
)}

これで、チェックをつけた処理済み項目のテキストは、クラスの追加によりスタイルが変わります(図002)。

図002■処理済み項目のスタイルが変わる

図002

05 Actionのtypeを定数として定める

Actionのtypeは、Action Creatorのほか、Reducerでも使います。数が増えることにも備えて、文字列のままでなく定数にした方が便利でしょう。その変更を加えたAction CreatorとReducerのふたつのモジュールのコード全体は、以下のコード002と003のとおりです。

コード002■src/actions/index.js


export const ADD_TODO = 'ADD_TODO';
export const TOGGLE_TODO = 'TOGGLE_TODO';

let nextTodoId = 0;
export const addTodo = (text) => ({
	type: ADD_TODO,
	id: nextTodoId++,
	text
});

export const toggleTodo = (id) => ({
	type: TOGGLE_TODO,
	id
});

コード003■src/reducers/todos.js


import { ADD_TODO, TOGGLE_TODO } from '../actions'

const todos = (state = [], action) => {
	switch (action.type) {
		case ADD_TODO:
			return [
				...state,
				{
					id: action.id,
					text: action.text,
					completed: false
				}
			];
		case TOGGLE_TODO:
			return state.map((todo) =>
				(todo.id === action.id)
					? {...todo, completed: !todo.completed}
					: todo
			)
		default:
			return state;
	}
};

export default todos;

そして、書き替えたコンポーネントのモジュールについては、以下のコード004および005に全体をまとめます。

コード004■src/components/TodoList.js


import React from 'react';
import { connect } from 'react-redux';
import { toggleTodo } from '../actions';
import Todo from './Todo';

const TodoList = ({ todos, toggleTodo }) => (
	<ul className="todo-list">
		{todos.map((todo) =>
			<Todo
				key={todo.id}
				{...todo}
				onClick={() => toggleTodo(todo.id)}
			/>
		)}
	</ul>
);

const mapStateToProps = (state) => ({
	todos: state.todos
});

const mapDispatchToProps = (dispatch) => ({
	toggleTodo: (id) => dispatch(toggleTodo(id))
});

export default connect(
	mapStateToProps,
	mapDispatchToProps
)(TodoList);

コード005■src/components/Todo.js


import React from 'react';
import classNames from 'classnames';

const Todo = ({ onClick, completed, text }) => (
	<li
		onClick={onClick}
		className={classNames(
			"todo-item",
			{"todo-item__text--completed": completed}
		)}
	>
		{completed ? "👌" : "👋"}{" "}
		<span>
			{text}
		</span>
	</li>
);

export default Todo;

サンプル001■react-redux-todos-03

React + Redux入門


作成者: 野中文雄
作成日: 2019年8月9日


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