サイトトップ

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

HTML5テクニカルノート

React + Redux入門 05: PropTypesで型を確かめる


Reactに備わる型チェックの機能がPropTypesです(「PropTypesを用いた型チェック」参照)。コンポーネントに与えられるプロパティ(props)のデータを、さまざまなかたちで確かめられます。「React + Redux入門 04: リスト項目のフィルタを加える」で書いたコードにPropTypesの型チェックを加えましょう。なお、パフォーマンスの理由により、チェックが働くのは開発のときだけです。

01 PropTypesをどう使うか

モジュールsrc/components/AddTodo.jsの関数コンポーネントAddTodoは、connect()関数により与えられたdispatch()を引数オブジェクトのプロパティとして受け取ります。PropTypesを使えば、この型が確かめられるのです。

つぎのようにprop-typesからPropTypesをインポートすると、コンポーネントにpropTypesが割り当てられます。与えるオブジェクトに加えるのが、チェックするプロパティ(dispatch)です。PropTypesのプロパティで型を決めます。関数(function)はPropTypes.funcです。そのままでは、省くことのできる(オプション)扱いになります。必須であることを示すのが、あとに添えたisRequiredです。

src/components/AddTodo.js

import PropTypes from 'prop-types';

const AddTodo = ({ dispatch }) => {

};

AddTodo.propTypes = {
	// dispatch: PropTypes.string  // 型が合わないので警告が示される
	dispatch: PropTypes.func.isRequired
};

export default connect()(AddTodo);

コメントアウトしたコードのように合わない型を定めると、つぎのような警告が示されます。なお、PropTypes.stringは文字列の型です。

Warning: Failed prop type: Invalid prop dispatch of type function supplied to AddTodo, expected string.

モジュールsrc/components/Link.jssrc/components/Todo.jsも、同じようにプロパティのデータ型が定められます。PropTypesのバリデータプロパティは、つぎの表001のとおりです。前掲src/components/AddTodo.jsと合わせて、以下のコード001から003にそれぞれモジュールの記述全体を掲げます。

表001■PropTypesのバリデータプロパティ

プロパティ データ型
PropTypes.array 配列
PropTypes.bool ブール(論理)値
PropTypes.func 関数
PropTypes.number 数値
PropTypes.object オブジェクト
PropTypes.string 文字列
PropTypes.symbol symbol
PropTypes.node ノード

コード001■src/components/AddTodo.js


import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { addTodo } from '../actions';

const AddTodo = ({ dispatch }) => {
	let input;
	return (
		<div>
			<form onSubmit={(event) => {
				event.preventDefault();
				const text = input.value.trim();
				input.value = '';
				if (!text) {
					return;
				}
				dispatch(addTodo(text));
			}}>
				<input ref={(element) => input = element} />
				<button type="submit">
					Add Todo
				</button>
			</form>
		</div>
	);
};

AddTodo.propTypes = {
	dispatch: PropTypes.func.isRequired
};

export default connect()(AddTodo);

コード002■src/components/Link.js


import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { setVisibilityFilter } from '../actions';

const Link = ({ active, children, onClick }) => (
	<button
		onClick={onClick}
		disabled={active}
		style={{
			marginLeft: '4px',
		}}
	>
		{children}
	</button>
);

const mapStateToProps = (state, ownProps) => ({
	active: ownProps.filter === state.visibilityFilter
});

const mapDispatchToProps = (dispatch, ownProps) => ({
	onClick: () => dispatch(setVisibilityFilter(ownProps.filter))
});

Link.propTypes = {
    active: PropTypes.bool.isRequired,
    children: PropTypes.node.isRequired,
    onClick: PropTypes.func.isRequired
};

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

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


import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

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

Todo.propTypes = {
	onClick: PropTypes.func.isRequired,
	completed: PropTypes.bool.isRequired,
	text: PropTypes.string.isRequired
};

export default Todo;

02 オブジェクトのプロパティに型を定める

プロパティがオブジェクトの場合について考えましょう。そのため、試しにモジュールsrc/components/TodoList.jssrc/components/Todo.jsをつぎのように書き替えたとします。

src/components/TodoList.js

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

				// {...todo}
				todo={todo}

			/>
		)}
	</ul>
);

src/components/Todo.js

// const Todo = ({ onClick, completed, text }) => (
const Todo = ({ onClick, todo }) => {
    const completed = todo.completed;
    const text = todo.text;
    return (

	);
};

このとき、関数コンポーネントTodoが受け取るオブジェクトのプロパティtodoのデータ型をどのように定めたらよいかです。PropTypes.objectは使えます。けれども、それではプロパティがオブジェクトであることしか決まりません。どういうプロパティにどのような値が納められているかわからないのです。

src/components/Todo.js

Todo.propTypes = {
	// completed: PropTypes.bool.isRequired,
	// text: PropTypes.string.isRequired
	todo: PropTypes.object.isRequired
};

このようなときには、関数PropTypes.shape()を用いてください。引数のオブジェクトにプロパティとPropTypesのバリデータがつぎのように書き表せるのです。オブジェクトtodoにはほかにもプロパティ(id)があります。けれど、そのデータ型をチェックしなくてよいなら省いても構いません。

src/components/Todo.js

Todo.propTypes = {
	
	// todo: PropTypes.object.isRequired
	todo: PropTypes.shape({
		completed: PropTypes.bool.isRequired,
		text: PropTypes.string.isRequired
	}).isRequired
};

モジュールsrc/components/TodoList.jssrc/components/Todo.jsは、もとに戻します。

03 オブジェクトの配列に型を定める

モジュールsrc/components/TodoList.jsの関数コンポーネントは、オブジェクトの配列(todos)と関数(toggleTodo)をプロパティとして受け取ります。この配列をどのように型づけけるかです。

src/components/TodoList.js

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

		)}
	</ul>
);

オブジェクトの場合と同じく、PropTypes.arrayでは配列であることしか決められません。それに対して、PropTypes.arrayOf()を用いれば、引数にバリデータが与えられます。さらに、PropTypes.shape()も引数にできるのです。つぎのように、要素のオブジェクトに型が定められます。

src/components/TodoList.js

import PropTypes from 'prop-types';

const TodoList = ({ todos, toggleTodo }) => (

);

TodoList.propTypes = {
	todos: PropTypes.arrayOf(PropTypes.shape({
		id: PropTypes.number.isRequired,
		completed: PropTypes.bool.isRequired,
		text: PropTypes.string.isRequired
	}).isRequired).isRequired,

};

書き改めたモジュールsrc/components/TodoList.jsの記述全体は、つぎのコード004のとおりです。サンプル001をCodePenに掲げました。

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


import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { toggleTodo, VisibilityFilters } 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 getVisibleTodos = (todos, filter) => {
	switch (filter) {
		case VisibilityFilters.SHOW_ALL:
			return todos
		case VisibilityFilters.SHOW_COMPLETED:
			return todos.filter(t => t.completed)
		case VisibilityFilters.SHOW_ACTIVE:
			return todos.filter(t => !t.completed)
		default:
			throw new Error('Unknown filter: ' + filter)
	}
}

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

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

TodoList.propTypes = {
	todos: PropTypes.arrayOf(PropTypes.shape({
		id: PropTypes.number.isRequired,
		completed: PropTypes.bool.isRequired,
		text: PropTypes.string.isRequired
	}).isRequired).isRequired,
	toggleTodo: PropTypes.func.isRequired
};

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

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

React + Redux入門


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


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