React + TypeScript: React Hook Formでフォーム入力値をまとめて簡単に取得・検証する

[React Hook Form](https://react-hook-form.com/jp)は、フォームの入力データを検証まで含めて、まとめて簡単に扱えるライブラリです。ただ、導入のページ(「[はじめる](https://react-hook-form.com/jp/get-started)」)にコード例は示されているものの、説明があまりありません。本稿は、その中から基本的なコード例8つを採り上げ、公式ドキュメントの引用やリンクも加えて解説します。コード例はわかりやすい(あるいは動く)ように手直しし、CodeSandboxにサンプルを掲げました。


# インストール
React Hook Formは、`npm install`コマンドでつぎのようにインストールします。

```terminal
npm install react-hook-form
```

アプリケーションを手もとでつくるには、[Create React App](https://ja.reactjs.org/docs/create-a-new-react-app.html#create-react-app)を使うのがよいでしょう。本稿のコード例の場合には、TypeScriptのテンプレートを加えてください(「[React + TypeScriptのひな形作成とFullCalendarのインストール](https://qiita.com/FumioNonaka/items/1810f7e211638988af62#react--typescript%E3%81%AE%E3%81%B2%E3%81%AA%E5%BD%A2%E4%BD%9C%E6%88%90%E3%81%A8fullcalendar%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)」参照)。


# 基本的な使い方
まずは、React Hook Formの基本的な使い方です。細かな構文は、あとの項で説明します。とりあえず、フックをどのように用いるか、大まかな仕組みについてご理解ください。

コード例の動きは、つぎのCodeSandboxのサンプル001で確かめられます。また、以下のコード001がアプリケーションモジュール(`src/App.tsx`)の記述です。なお、最小限のCSSの定めについては、`src/styles.css`をご覧ください。

#### サンプル001■[React + TypeScript: React Hook Form basic example](https://codesandbox.io/s/react-typescript-react-hook-form-basic-example-ysz8c?file=/src/App.tsx)
[![2112001_007.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128949/295f888f-df2a-f3bb-8eb8-cfa693127177.png)
\>> CodeSandboxへ](https://codesandbox.io/s/react-typescript-react-hook-form-basic-example-ysz8c?file=/src/App.tsx)

アプリケーションモジュール(`src/App.tsx`)の処理の流れはつぎのとおりです。[`useForm`](https://react-hook-form.com/api/useform)フックの戻り値から、[`register`](https://react-hook-form.com/api/useform/register)を取り出します。フックで扱うフォーム要素には`register`で一意の名前を渡して登録しなければなりません。戻り値は、登録したフォーム要素に[スプレッド構文](https://qiita.com/FumioNonaka/items/58358a29850afd7a0f37#%E3%82%B9%E3%83%97%E3%83%AC%E3%83%83%E3%83%89%E6%A7%8B%E6%96%87%E3%82%92%E4%BD%BF%E3%81%86)で加えてください。

`useForm`フックから得た[`handleSubmit`](https://react-hook-form.com/api/useform/handlesubmit)は、フォームの入力を確かめたうえで、引数に渡した関数(onSubmit)を呼び出すハンドラです。`SubmitHandler`がその型づけになります。データ(`data`)が出力されますので、Consoleを開いてお確かめください。

[`watch`](https://react-hook-form.com/api/useform/watch)は、引数に渡した名前のフォーム要素の入力値を監視して返します。[`formState`](https://react-hook-form.com/api/useform/formstate)は、名前が示すとおりフォームの状態を情報として収めたオブジェクトです。`errors`プロパティから、登録した名前ごとにエラーが取り出せます。

#### コード001■React Hook Formの基本的な使い方
```react:src/App.tsx
import { useForm, SubmitHandler } from 'react-hook-form';
import './styles.css';

type Inputs = {
	example: string;
	exampleRequired: string;
};
export default function App() {
	const {
		register,
		handleSubmit,
		watch,
		formState: { errors }
	} = useForm<Inputs>();
	const onSubmit: SubmitHandler<Inputs> = (data) => console.log('onSubmit:', data);
	console.log('watch:', watch('example')); // watchは引数に渡した名前の入力値を監視する
	return (
		/* handleSubmitはフォームの入力を確かめたうえで、引数に渡した関数(onSubmit)を呼び出す */
		<form onSubmit={handleSubmit(onSubmit)}>
			{/* register関数の呼び出しにより、フォーム入力の要素を引数の名前で登録する */}
			<input defaultValue="test" {...register('example')} />
			{/* register関数の第2引数には、HTML標準フォームデータ検証のルールが渡せる */}
			<input {...register('exampleRequired', { required: true })} />
			{/* データ検証に失敗するとerrorsが返され、登録した名前で取り出せる */}
			{errors.exampleRequired && (
				<span style={{ color: 'red' }}>This field is required</span>
			)}
			<input type="submit" />
		</form>
	);
}
```


# フォームの要素をフックに登録する
React Hook Formを用いるときに大事なのは、まず「[非制御コンポーネント](https://ja.reactjs.org/docs/uncontrolled-components.html)」をフックに登録することです。`useForm`フックから取り出した[`register`](https://react-hook-form.com/api/useform/register)関数で登録し、引数にキーとなる一意の識別子(`name`)を渡してください(第2引数は省略可)。関数の戻り値は、登録したフォーム要素をフックで扱うためのオブジェクトです。登録したフォーム要素に[スプレッド構文](https://qiita.com/FumioNonaka/items/58358a29850afd7a0f37#%E3%82%B9%E3%83%97%E3%83%AC%E3%83%83%E3%83%89%E6%A7%8B%E6%96%87%E3%82%92%E4%BD%BF%E3%81%86)で加えます。

```typescript
register: (name: string, RegisterOptions?) => ({ onChange, onBlur, name, ref })
```
`useForm`フックから得られる[`handleSubmit`](https://react-hook-form.com/api/useform/handlesubmit)関数は、登録したフォーム要素のデータを検証したうえで、引数のコールバック関数により送信します(第2引数は省略可)。コールバックに渡される引数(`data`)は、登録したフォーム要素の名前と値をプロパティとするオブジェクトです。

```typescript
handleSubmit: ((data: Object, e?: Event) => void, (errors: Object, e?: Event) => void) => Function
```
CodeSandboxにサンプル002を公開しました。また、モジュール`src/App.tsx`の中身が、以下のコード002です。

#### サンプル002■[React + TypeScript: React Hook Form basic example 2](https://codesandbox.io/s/react-typescript-react-hook-form-basic-example-2-78jme?file=/src/App.tsx)
[![2112001_001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128949/39032a92-8c15-f252-fbef-3ce9d238621b.png)
\>> CodeSandboxへ](https://codesandbox.io/s/react-typescript-react-hook-form-basic-example-2-78jme?file=/src/App.tsx)


#### コード002■フックに登録したフォーム要素のデータを確かめる
```react:src/App.tsx
import { useForm, SubmitHandler } from 'react-hook-form';
import './styles.css';

enum GenderEnum {
	female = 'female',
	male = 'male',
	other = 'other'
}
interface IFormInput {
	firstName: String;
	gender: GenderEnum;
}
export default function App() {
	const { register, handleSubmit } = useForm<IFormInput>();
	const onSubmit: SubmitHandler<IFormInput> = (data) => console.log(data);
	return (
		<form onSubmit={handleSubmit(onSubmit)}>
			<label>
				First Name
				<input {...register('firstName')} />
			</label>
			<label>
				Gender Selection
				<select {...register('gender')}>
					<option value="female">female</option>
					<option value="male">male</option>
					<option value="other">other</option>
				</select>
			</label>
			<input type="submit" />
		</form>
	);
}
```


# フォームのデータを検証する
`register`関数の第2引数には、HTML標準フォームデータ検証のルールが渡せます(「[クライアント側のフォームデータ検証](https://developer.mozilla.org/docs/Learn/Forms/Form_validation)」参照)。以下のサンプル003で用いたルールは、つぎの表001のとおりです(コード003)。

#### 表001■`register`関数の第2引数に渡せるHTML標準フォームデータ検証のルール(一部)
| ルール | 説明 |
|---|---|
| **required**: _boolean_ | 値が入力されなければならないかどうか |
| **min**: _number_ | 値の最小値 |
| **max**: _number_ | 値の最大値 |
| **minLength**: _number_ | データ長の最小値 |
| **maxLength**: _number_ | データ長の最大値 |
| **pattern**: _RegExp_ | データが合致するかどうか調べる[正規表現](https://developer.mozilla.org/docs/Web/JavaScript/Guide/Regular_Expressions) |

#### サンプル003■[React + TypeScript: React Hook Form basic example 3](https://codesandbox.io/s/react-typescript-react-hook-form-basic-example-3-cukos?file=/src/App.tsx)
[![2112001_002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128949/42b90aa5-8ebc-6330-9e8c-cee9265c31f3.png)
\>> CodeSandboxへ](https://codesandbox.io/s/react-typescript-react-hook-form-basic-example-3-cukos?file=/src/App.tsx)

#### コード003■フォームに入力したデータをルールに照らして検証する
```react:src/App.tsx
import { useForm, SubmitHandler } from 'react-hook-form';
import './styles.css';

interface IFormInput {
	firstName: String;
	lastName: string;
	age: number;
}
export default function App() {
	const { register, handleSubmit } = useForm<IFormInput>();
	const onSubmit: SubmitHandler<IFormInput> = (data) => console.log(data);
	return (
		<form onSubmit={handleSubmit(onSubmit)}>
			<label>
				firstName
				<input {...register('firstName', { required: true, maxLength: 20 })} />
			</label>
			<label>
				lastName
				<input {...register('lastName', { pattern: /^[A-Za-z]+$/i })} />
			</label>
			<label>
				age
				<input type="number" {...register('age', { min: 18, max: 99 })} />
			</label>
			<input type="submit" />
		</form>
	);
}
```


# コンポーネント化した要素をフォームに組み込む
React Hook Formは、フォーム要素に`ref`を与えて管理します。[`register`](https://react-hook-form.com/api/useform/register)
関数は、要素に内部的に`ref`を加える役割があるのです(表002参照)。子コンポーネントをフォームに組み込むには、何らかのかたちで`ref`を定めなければなりません。

ひとつのやり方は、親コンポーネントから子に`register`関数を渡して登録することです。以下のコード004では、モジュール`src/Input.tsx`が親から受け取った`register`で要素を登録しました。

#### 表002■`register`関数が返すオブジェクトのプロパティ
| プロパティ | 説明 |
|---|---|
| **onChange**: _ChangeHandler_ | 登録したフォーム要素の入力値が変わったときに呼び出すイベントハンドラ |
| **onBlur**: _ChangeHandler_ | 登録したフォーム要素からフォーカスが外れたときに呼び出すイベントハンドラ |
| **ref**: _React.Ref\<any\>_ | 登録したフォーム要素をフックが参照するためのrefオブジェクト |
| **name**: _string_ | 登録したフォーム要素に与えた名前 |

もうひとつのやり方は、親が`ref`を子に渡すことです。ただし、親が子に定めた`ref`は、子コンポーネントの引数のプロパティ(`props`)からは受け取れません。

> 通常の関数またはクラスコンポーネントは`ref`引数を受け取らず、`ref`は`props`からも利用できません。
(「[DOM コンポーネントに ref をフォワーディングする](https://ja.reactjs.org/docs/forwarding-refs.html#forwarding-refs-to-dom-components)」)

親が与えた`ref`を受け取るために用いるのが`React.forwardRef`で(「[ref のフォワーディング](https://ja.reactjs.org/docs/forwarding-refs.html)」参照)、引数に定めるのが関数コンポーネントです。コード004のモジュール`src/Select.tsx`は、引数の関数コンポーネントが第2引数で`ref`を受け取れるので、戻り値のJSXフォーム要素に定めました。なお、`ref`は親から`register`の呼び出しにより与えられることは前述のとおりです(表002)。

#### サンプル004■[React + TypeScript: React Hook Form basic example 4](https://codesandbox.io/s/react-typescript-react-hook-form-basic-example-4-rkbdf?file=/src/App.tsx)
[![2112001_004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128949/d1bd823a-7d60-2e1c-19e0-17140a6fff53.png)
\>> CodeSandboxへ](https://codesandbox.io/s/react-typescript-react-hook-form-basic-example-4-rkbdf?file=/src/App.tsx)

#### コード004■要素を子コンポーネントに分けてアプリケーションのフォームに組み込む
```react:src/App.tsx
import React from 'react';
import { useForm, SubmitHandler } from 'react-hook-form';
import { Input } from './Input';
import { Select } from './Select';
import './styles.css';

export interface IFormValues {
	'First Name': string;
	Age: number;
}
function App() {
	const { register, handleSubmit } = useForm<IFormValues>();
	const onSubmit: SubmitHandler<IFormValues> = (data) => {
		alert(JSON.stringify(data));
	};
	return (
		<form onSubmit={handleSubmit(onSubmit)}>
			<Input label="First Name" register={register} required />
			<Select label="Age" {...register('Age')} />
			<input type="submit" />
		</form>
	);
}

export default App;
```

```react:src/Input.tsx
import { Path, UseFormRegister } from 'react-hook-form';
import { IFormValues } from './App';

type InputProps = {
	label: Path<IFormValues>;
	register: UseFormRegister<IFormValues>;
	required: boolean;
};
// <input>要素を含んだ子コンポーネント
export const Input = ({ label, register, required }: InputProps) => (
	<label>
		{label}
		<input {...register(label, { required })} />
	</label>
);
```

```react:src/Select.tsx
import React from 'react';
import { UseFormRegister } from 'react-hook-form';
import { IFormValues } from './App';

// React.forwardRefでrefオブジェクトを渡すこともできる
export const Select = React.forwardRef<
	HTMLSelectElement,
	{ label: string } & ReturnType<UseFormRegister<IFormValues>>
>(({ onChange, onBlur, name, label }, ref) => (
	<label>
		{label}
		<select name={name} ref={ref} onChange={onChange} onBlur={onBlur}>
			<option value="20">20</option>
			<option value="30">30</option>
		</select>
	</label>
));
```


# UIライブラリと組み合わせて使う
React Hook Formに、[MUI](https://mui.com/)や[React Select](https://react-select.com/home)といった他のUIライブラリのコンポーネントを組み込むこともできます。そうしたコンポーネントを包むのが[`Controller`](https://react-hook-form.com/api/usecontroller/controller)です(表003参照)。コンポーネントに定める`render`のコールバックでレンダープロップを受け取り、UIコンポーネントに加えて返してください。これらのプロパティには`ref`も含まれます。

#### 表003■`Controller`コンポーネントに与えられるプロパティ
| プロパティ | 説明 |
|---|---|
| **name**: _string_ | フォーム入力要素に与える一意の識別子(必須) |
| **control**: _Object_ | `useForm`の戻り値から得られる`control`オブジェクト |
| **render**: _Function_ | [レンダープロップ](https://ja.reactjs.org/docs/render-props.html)(render prop)からレンダーするReact要素を返す関数。 |
| **defaultValue**: _any_ | フォーム入力要素のデフォルト値 |
| **rules**: _Object_ | フォームデータの検証ルール(`register`と同じ) |

MUIの`Button`と`Input`およびReact Selectをフォームに組み込んだのが、つぎのサンプル005です(コード005)。`render`コールバックの引数(`field`)からフォーム要素に展開されるプロパティにより`ref`が与えられます。

#### サンプル005■[React + TypeScript: React Hook Form basic example 5](https://codesandbox.io/s/react-typescript-react-hook-form-basic-example-5-tu3f5?file=/src/App.tsx)
[![2112001_005.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128949/95731bf2-5621-1cdf-c82e-1cb25625c6e0.png)
\>> CodeSandboxへ](https://codesandbox.io/s/react-typescript-react-hook-form-basic-example-5-tu3f5?file=/src/App.tsx)

#### コード005■UIライブラリの要素を組み込んで制御する
```react:src/App.tsx
import React from 'react';
import Select from 'react-select';
import { useForm, Controller, SubmitHandler } from 'react-hook-form';
import { Button, Input } from '@mui/material';
import './styles.css';

interface IFormInput {
	firstName: string;
	iceCreamType: { label: string; value: string };
}
export default function App() {
	const { control, handleSubmit } = useForm<IFormInput>();
	const onSubmit: SubmitHandler<IFormInput> = (data) => {
		console.log(data);
	};
	return (
		<form onSubmit={handleSubmit(onSubmit)}>
			<label>
				First Name
				<Controller
					name="firstName"
					control={control}
					defaultValue=""
					render={({ field }) => <Input {...field} />}
				/>
			</label>
			<label>
				Ice Cream Preference
				<Controller
					name="iceCreamType"
					control={control}
					render={({ field }) => (
						<Select
							{...field}
							options={[
								{ value: "chocolate", label: "Chocolate" },
								{ value: "strawberry", label: "Strawberry" },
								{ value: "vanilla", label: "Vanilla" }
							]}
						/>
					)}
				/>
			</label>
			<Button type="submit" variant="outlined">
				送信
			</Button>
		</form>
	);
}
```


# 制御されたコンポーネントをフォームに組み込む
React Hook Formでは、非制御コンポーネントおよびHTML標準の`<input>`を用いることが想定されています。けれど、他のUIライブラリや汎用化したフォーム要素のコンポーネントを使いたいことが少なくないでしょう。その場合、組み込むコンポーネントには`ref`を定めなければなりません。

## `Controller`コンポーネントを使う
ひとつのやり方は、前項と同じく`Controller`で包むことです。`render`コールバックが受け取った引数オブジェクトをコンポーネントに展開して与えれば、その中に`ref`も含まれています。

#### サンプル006■[React + TypeScript: React Hook Form basic example 6](https://codesandbox.io/s/react-typescript-react-hook-form-basic-example-6-9ycfb?file=/src/App.tsx)
[![2112001_008.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128949/8d0b122b-7627-bc15-e25f-8c3e5d6cbb5e.png)
\>> CodeSandboxへ](https://codesandbox.io/s/react-typescript-react-hook-form-basic-example-6-9ycfb?file=/src/App.tsx)

#### コード006■`Controller`コンポーネントでフォーム入力要素を組み込む
```react:src/App.tsx
import React from 'react';
import { useForm, Controller, SubmitHandler } from 'react-hook-form';
import { TextField, Checkbox } from '@mui/material';
import './styles.css';

const defaultValues = {
	TextField: '',
	MyCheckbox: false
};
interface IFormInputs {
	TextField: string;
	MyCheckbox: boolean;
}
function App() {
	const { handleSubmit, control } = useForm<IFormInputs>({
		defaultValues
	});
	const onSubmit: SubmitHandler<IFormInputs> = (data) => console.log(data);
	return (
		<form onSubmit={handleSubmit(onSubmit)}>
			<label>
				MUI TextField
				<Controller
					render={({ field }) => <TextField {...field} />}
					name="TextField"
					control={control}
				/>
			</label>
			<label>
				MUI Checkbox
				<Controller
					name="MyCheckbox"
					control={control}
					rules={{ required: true }}
					render={({ field }) => <Checkbox {...field} />}
				/>
			</label>
			<input type="submit" />
		</form>
	);
}

export default App;
```

このとき気をつけなければならないのは、`useForm`フックにデフォルト値([`defaultValues`](https://react-hook-form.com/api/useform))を渡すことです。引数なしに呼び出すと、前掲コード006では`TextField`コンポーネントについて、つぎのような警告が示されてしまいます。

> Warning: A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component.

なお、デフォルト値はフックでなく、`Controller`コンポーネントの`defaultValue`で与えることもできます(前掲表003参照)。

# `useController`フックを使う
もうひとつのやり方は、子コンポーネントから[`useController`](https://react-hook-form.com/api/usecontroller)フックを呼び出すことです。引数には親から受け取るプロパティを渡します。戻り値から得られる`field`に`ref`が含まれますので、フォーム入力の要素に展開して加えてください。なお、戻り値から`fieldState`を取り出せば、入力値の検証もできます。

#### サンプル007■[React + TypeScript: React Hook Form basic example 7](https://codesandbox.io/s/react-typescript-react-hook-form-basic-example-7-umepi?file=/src/App.tsx)
[![2112001_006.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128949/83475d5c-2cf7-a6aa-fc6a-2f73d6d3292b.png)
\>> CodeSandboxへ](https://codesandbox.io/s/react-typescript-react-hook-form-basic-example-7-umepi?file=/src/App.tsx)

#### コード007■`useController`フックで子コンポーネントをフォームに組み込む
```react:src/App.tsx
import React from 'react';
import { useForm } from 'react-hook-form';
import { Input } from './Input';
import './styles.css';

export type FormValues = {
	FirstName: string;
};
function App() {
	const { handleSubmit, control } = useForm<FormValues>({
		defaultValues: {
			FirstName: ''
		},
		mode: 'onChange'
	});
	const onSubmit = (data: FormValues) => console.log(data);
	return (
		<form onSubmit={handleSubmit(onSubmit)}>
			<Input control={control} name="FirstName" rules={{ required: true }} />
			<input type="submit" />
		</form>
	);
}
export default App;
```

```react:src/Input.tsx
import { useController, UseControllerProps } from 'react-hook-form';
import { FormValues } from './App';

export const Input = (props: UseControllerProps<FormValues>) => {
	const { field, fieldState } = useController(props);
	return (
		<div>
			<input {...field} placeholder={props.name} />
			<p>{fieldState.isTouched && 'Touched'}</p>
			<p>{fieldState.isDirty && 'Dirty'}</p>
			<p>{fieldState.invalid ? 'invalid' : 'valid'}</p>
		</div>
	);
};
```


# エラーを扱う
`useForm`の戻り値から得られる[`formState`](https://react-hook-form.com/api/useform/formstate)で、登録したフォーム要素の情報が得られます。エラーを含むのが`errors`オブジェクトです。登録した名前(`name`)をプロパティとして、個別に取り出せます。

#### サンプル008■[React + TypeScript: React Hook Form basic example 8](https://codesandbox.io/s/react-typescript-react-hook-form-basic-example-8-n5dwt?file=/src/App.tsx)
[![2112001_009.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128949/4a99b5ac-4fa7-7581-8a87-1ecb1488ca57.png)
\>> CodeSandboxへ](https://codesandbox.io/s/react-typescript-react-hook-form-basic-example-8-n5dwt?file=/src/App.tsx)

#### コード008■登録したフォーム要素のデータ検証に対するエラーを調べる
```react:src/App.tsx
import React from 'react';
import { useForm, SubmitHandler } from 'react-hook-form';
import './styles.css';

interface IFormInputs {
	firstName: string;
	lastName: string;
}
const onSubmit: SubmitHandler<IFormInputs> = (data) => console.log(data);
export default function App() {
	const {
		register,
		formState: { errors },
		handleSubmit
	} = useForm<IFormInputs>();
	return (
		<form onSubmit={handleSubmit(onSubmit)}>
			<input {...register('firstName', { required: true })} />
			{errors.firstName && 'First name is required'}
			<input {...register('lastName', { required: true })} />
			{errors.lastName && 'Last name is required'}
			<input type="submit" />
		</form>
	);
}
```


# 公式サイトについて
React Hook Formの公式サイトには[日本語版](https://react-hook-form.com/jp/)があります。ただ、導入部分のページだけで、説明もあまりありまません。[英語サイト](https://react-hook-form.com/)の方が同じページでももう少し情報は多く、ドキュメントへのリンクも添えられています。日本語で書かれていてもそもそも情報が少ないので、ここは英語サイトを見るべきでしょう。

本稿は、[`Get Started`](https://react-hook-form.com/get-started/)のコード例をもとに、解説を加えています。けれど、公式ページの中にはそのままコピー&ペーストして動作しないものがありました(逆に、不要なコードもあったり)。もっとも、CodeSandboxのサンプルを見ると動いています。ただ、公式ページに掲げられているコード例とかなり違っているものが少なくありません(たとえば、CSSはかなりゴテゴテに定められています)。

本稿のコード例はそのまま動くように整え、リンクしたCodeSandboxのサンプルと基本同じです(CSSは最小限加えました)。また、別モジュールにできるコンポーネントは分け、多少なりとも実践的にしました。