✅ はじめに
こんにちは!このシリーズでは、React + Vite + TypeScript + MUIを使ってモダンなWebアプリを開発する方法をステップバイステップで解説しています。
第7回となる今回は、「フォームをコンポーネント単位で分割する設計手法」について解説します。前回作成したフォームをベースに、入力フィールドを個別のコンポーネントに分割し、保守性と再利用性を高める実装方法を学んでいきます。
📦 使用する技術スタック
ツール | バージョン | 説明 |
---|---|---|
React Hook Form | v7.x | 高性能なフォームバリデーションライブラリ |
MUI | v5.x | マテリアルデザインベースのUIコンポーネント |
TypeScript | 5.x | 型安全なJavaScript |
Vite | 5.x | 高速な開発環境とビルドツール |
🚀 実装手順
1. プロジェクトの作成
まずは、Viteを使って新しいプロジェクトを作成します:
npm create vite@latest react-form-components -- --template react-ts cd react-form-components npm install
2. 必要なパッケージのインストール
MUIとReact Hook Formをインストールします:
npm install @mui/material @emotion/react @emotion/styled @mui/icons-material react-hook-form
3. プロジェクト構成
今回のプロジェクトは以下のような構成になります:
src/ ├── components/ │ ├── NameField.tsx │ └── EmailField.tsx ├── FormComponent.tsx └── App.tsx
4. 入力フィールドコンポーネントの作成
まず、src/components/NameField.tsxを作成します:
import { TextField } from "@mui/material"; import { useForm } from "react-hook-form"; import type { FC } from "react"; type Props = { register: ReturnType<typeof useForm>["register"]; error?: string; }; const NameField: FC<Props> = ({ register, error }) => { return ( <TextField fullWidth label="名前" {...register("name", { required: "名前は必須です" })} error={Boolean(error)} helperText={error} /> ); }; export default NameField;
次に、src/components/EmailField.tsxを作成します:
import { TextField } from "@mui/material"; import { useForm } from "react-hook-form"; import type { FC } from "react"; type Props = { register: ReturnType<typeof useForm>["register"]; error?: string; }; const EmailField: FC<Props> = ({ register, error }) => { return ( <TextField fullWidth label="メール" {...register("email", { required: "メールは必須です", pattern: { value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, message: "メール形式が不正です", }, })} error={Boolean(error)} helperText={error} /> ); }; export default EmailField;
5. メインフォームコンポーネントの作成
src/FormComponent.tsxを作成します:
import { useForm, type FieldValues, type UseFormRegister } from "react-hook-form"; import { Box, Button, Container, Typography } from "@mui/material"; import NameField from "./components/NameField"; import EmailField from "./components/EmailField"; import type { FC } from "react"; type FormValues = { name: string; email: string; }; const FormComponent: FC = () => { const { register, handleSubmit, formState: { errors }, } = useForm<FormValues>(); const onSubmit = (data: FormValues) => { console.log("送信データ:", data); alert(JSON.stringify(data, null, 2)); }; return ( <Container maxWidth="sm"> <Box mt={5}> <Typography variant="h5" gutterBottom> コンポーネント分割フォーム </Typography> <form onSubmit={handleSubmit(onSubmit)} noValidate> <Box display="flex" flexDirection="column" gap={2}> <NameField register={register as unknown as UseFormRegister<FieldValues>} error={errors.name?.message} /> <EmailField register={register as unknown as UseFormRegister<FieldValues>} error={errors.email?.message} /> <Box textAlign="center"> <Button type="submit" variant="contained" color="primary"> 送信 </Button> </Box> </Box> </form> </Box> </Container> ); }; export default FormComponent;
6. App.tsxの更新
最後に、src/App.tsxを更新してフォームを表示します:
import FormComponent from "./FormComponent"; import type { FC } from "react"; const App: FC = () => { return <FormComponent />; }; export default App;
🎯 主要なポイント
1. コンポーネント設計
- 単一責任の原則: 各コンポーネントは1つの役割に特化
- 再利用性: 入力フィールドを独立したコンポーネントとして実装
- 型安全性: TypeScriptによる厳密な型チェック
2. Props設計
- register関数: React Hook Formの登録関数を適切に受け渡し
- エラー処理: エラーメッセージを親から子へ伝播
- 型定義: 明確なPropsの型定義による安全性確保
3. フォーム管理
- 状態管理: useFormによる一元的なフォーム状態管理
- バリデーション: 各フィールドでの個別バリデーション定義
- 送信処理: handleSubmitによる統一的な送信処理
🔚 まとめ
今回は、React Hook Formを使用したフォームをコンポーネント単位に分割する設計手法について学びました。この設計により、以下のメリットが得られます:
- ✅ 保守性の向上: 各コンポーネントが独立して管理可能
- 🔄 再利用性の向上: 共通の入力フィールドを別のフォームでも使用可能
- 📝 コードの可読性向上: 責任範囲が明確で理解しやすい構造
📚 次回予告
【第8回】React Hook Form × MUI:ステップフォームの実装