この記事は READYFOR Advent Calendar 2020の13日目の記事です。
はじめに
こんにちは。今年の11月から READYFOR でフロントエンドエンジニアとして働いている菅原(@kotarella1110)です!
React と TypeScript が好きで、React Hook Form のメンバーだったりもします。React Hook Form は TypeScript で記述されているのですが、型の改善を中心にコントリビュートしております。
そこで、本記事では React Hook Form に関連した内容をお話しできればと思います。
React でフォームを実装する際のデザインパターン
React でフォームを実装する際のデザインパターンは以下の二パターンです。
- Controlled Components を使用する
- Uncontrolled Components を使用する
皆さんはどちらを採用していますか?
React 公式ではフォームの実装に Controlled Components の使用を推奨していることもあり、Uncontrolled Components の採用検討を無視し、Controlled Components を採用している場合がほとんどではないでしょうか? Redux Form や Formik などの主要なフォームライブラリも Controlled Components を採用しています。
ほとんどの場合では、フォームの実装には制御されたコンポーネント (controlled component) を使用することをお勧めしています。制御されたコンポーネントでは、フォームのデータは React コンポーネントが扱います。
しかし、React Hook Form は過小評価されてきた(?)Uncontrolled Components を採用しています。
なぜでしょうか?
Controlled Components
Controlled Components は React の状態を介して入力が制御されるコンポーネントを指します。入力フォーム要素の onChange
を使用して入力の状態を更新し、新しい値を value
に設定します。
入力する度に React の状態が更新されるため再レンダリングが発生しますが、入力値を直接制御することができます。
import React, { useState } from 'react';
const ControlledForm = () => {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [gender, setGender] = useState('male');
const handleSubmit = (e) => {
e.preventDefault();
alert(JSON.stringify({
name,
email,
gender,
}));
};
return (
<form onSubmit={handleSubmit}>
<input value={name} onChange={(e) => setName(e.target.value)} />
<input value={email} onChange={(e) => setEmail(e.target.value)} />
<select value={gender} onChange={(e) => setGender(e.target.value)}>
<option value="male">male</option>
<option value="female">female</option>
</select>
<button type="submit">Submit</button>
</form>
)
}
Uncontrolled Components
Controlled Components は React によって入力値を制御しますが、Uncontrolled Components は入力値を DOM 自身(ブラウザ)が制御します。入力する度に React の状態が更新されないため再レンダリングが発生しません。
これは React 固有ではなく、単なるネイティブ実装(に近い)です。
デフォルト値は value
の代わりに defaultValue
属性を指定することができます。
import React from 'react';
const UncontrolledForm = () => {
const handleSubmit = (e) => {
e.preventDefault();
alert(JSON.stringify({
name: e.target.name.value,
email: e.target.email.value,
gender: e.target.gender.value,
}));
};
return (
<form onSubmit={handleSubmit}>
<input name="name" />
<input name="email" />
<select name="gender" defaultValue="male">
<option value="male">male</option>
<option value="female">female</option>
</select>
<button type="submit">Submit</button>
</form>
)
}
Uncontrolled Components を使用する利点
上の二つの例を比較してご覧ください。
Uncontrolled Components は以下の利点があります。
シンプルでクリーンなアプローチだと思いませんか?
- コードの記述量が少ない
-
onChange
を使用した入力フォーム要素に値を設定するための定型コードが不要となる
-
- パフォーマンスが向上する
- 入力する度に再レンダリングが発生しない
- Controlled Components の場合、入力する度に再レンダリングが発生するため、フォームの状態がコンポーネントツリーの上位にある場合にパフォーマンスの問題を引き起こすことがある
- ネイティブ実装に近づくため移植性が高い
Controlled Components が必要なケースは以下のケースなどに限定され、ほとんどのケースで必要ないと思います。
- フォームの入力値を子コンポーネントに渡す必要がある場合
- 入力の度に再レンダリングを発生させる必要がある場合
- サジェスト表示付き検索入力欄など
- 複雑なフォームの実装の場合(複雑なフォーム ≠ 大きなフォーム)
- 相互依存関係のある入力フォーム要素など
React Hook Form はこれらの利点を踏まえ Uncontrolled Components を採用しています。
React Hook Form
React Hook Form はシンプルで使いやすいフォームバリデーション用の React フックです。
GitHub スター数が 15000 を超えるライブラリで、2020年の React Open Source Awards では Productivity Booster を受賞しました。
先に説明した通り、React Hook Form は Controlled Components の代わりに Unontrolled Components を活用し、それらの参照(ref
)などをカスタムフック(useForm
)に登録します。これにより、カスタムフックで各入力フォーム要素を完全にコントロールすることが可能になります。
import React from 'react';
import { useForm } from 'react-hook-form';
export default function App() {
const { register, handleSubmit, formState: { errors } } = useForm();
const onSubmit = (data) => alert(JSON.stringify(data));
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("name", { required: true })} />
{errors.name && "name is required"}
<input {...register("email")} />
<input {...register("age")} type="number" />
<select {...register("gender")}>
<option value="male">male</option>
<option value="female">female</option>
</select>
<button type="submit">Submit</button>
</form>
);
}
React Hook Form の使い方や API の詳細については日本語の公式ドキュメントにお任せして、筆者が推したい特徴について紹介させていただきます。
シンプルで使いやすい
Constraint Validation API をベースとした直感的でシンプルな API を提供しています。register
を使用して各入力フォーム要素の参照を登録するだけです。
超軽量
Uncontrolled Components を採用し、依存関係がゼロのためパッケージサイズが非常に小さいです(2020年12月時点で 9KB 以下)。
useForm
フックのみであれば僅か 6.54KB です。
パッケージサイズの比較:
パフォーマンス
Uncontrolled Components を採用しているため、入力の変更による再レンダリングの発生を防ぎ、パフォーマンスが向上します。
補足ですが、useForm
には mode
オプションが存在し、
入力の変更の度にバリデーションを行う onChange
モードを指定しても再レンダリングは発生しません(reValidateMode
も同様)。各入力フォーム要素の参照から値の変更をサブスクライブし、バリデーションは実行されますが React の状態は更新されないためです。バリデーションが成功から失敗に切り替わるタイミング(またはその逆のタイミング)などの必要なタイミングで再レンダリングを発生させます。
公式ドキュメントから再レンダリングの回数とマウント速度の、主要なフォームバリデーションライブラリとの比較を確認することができます。
再レンダリング回数の比較:
マウント速度の比較:
最後に
Controlled Components と Uncontrolled Components でどちらにもトレードオフがあるため、ユースケースに応じて適切な選択をしてください!
React Hook Form には今回紹介した特徴以外にも様々な特徴が存在します。また、Material-UI や React-Select などの制御された UI コンポーネントと組み合わせて使用するための Controller コンポーネントや、Yup や Joi などを使用したスキーマバリデーションのための resolver、そしてフォームの状態をデバッグするための DevTools など様々なオプションを提供しています。
是非一度お試し頂ければと思います。
アドベントカレンダー、明日の記事は
明日は @takaheraw さんの担当です!お楽しみに 🙌