Uncontrolled Components でシンプル且つ高パフォーマンスなフォームの実装 - React Hook Form


![READYFORアドベントカレンダー2020カバー画像.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83038/9bf23faf-6771-c83d-94cd-21827bf9c493.png)

**この記事は [READYFOR Advent Calendar 2020](https://qiita.com/advent-calendar/2020/readyfor)の13日目の記事です。**

## はじめに

こんにちは。今年の11月から READYFOR でフロントエンドエンジニアとして働いている菅原（[@kotarella1110](https://twitter.com/kotarella1110)）です！
React と TypeScript が好きで、[React Hook Form](https://react-hook-form.com) の[メンバー](https://react-hook-form.com/about-us)だったりもします。React Hook Form は TypeScript で記述されているのですが、型の改善を中心にコントリビュートしております。
そこで、本記事では React Hook Form に関連した内容をお話しできればと思います。

## React でフォームを実装する際のデザインパターン

React でフォームを実装する際のデザインパターンは以下の二パターンです。

1. [Controlled Components](https://ja.reactjs.org/docs/forms.html#controlled-components) を使用する
2. [Uncontrolled Components](https://ja.reactjs.org/docs/uncontrolled-components.html) を使用する

皆さんはどちらを採用していますか？
[React 公式ではフォームの実装に Controlled Components の使用を推奨している](https://ja.reactjs.org/docs/uncontrolled-components.html)こともあり、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 の状態が更新されるため再レンダリングが発生しますが、入力値を直接制御することができます。

```jsx
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` 属性を指定することができます。

```jsx
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 を超えるライブラリ](https://github.com/react-hook-form/react-hook-form)で、2020年の [React Open Source Awards](https://osawards.com/react/) では Productivity Booster を受賞しました。

先に説明した通り、React Hook Form は Controlled Components の代わりに Unontrolled Components を活用し、それらの参照（`ref`）などをカスタムフック（`useForm`）に登録します。これにより、カスタムフックで各入力フォーム要素を完全にコントロールすることが可能になります。

```jsx
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 の詳細については[日本語の公式ドキュメント](https://react-hook-form.com/jp/)にお任せして、筆者が推したい特徴について紹介させていただきます。

### シンプルで使いやすい

[Constraint Validation API](https://developer.mozilla.org/ja/docs/Web/Guide/HTML/HTML5/Constraint_validation) をベースとした直感的でシンプルな API を提供しています。`register` を使用して各入力フォーム要素の参照を登録するだけです。

### 超軽量

Uncontrolled Components を採用し、依存関係がゼロのためパッケージサイズが非常に小さいです（2020年12月時点で 9KB 以下）。
`useForm` フックのみであれば僅か 6.54KB です。

パッケージサイズの比較：

![compare-package-size.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83038/9202ef84-4776-b81f-9af8-e75912b9cb5f.jpeg)


### パフォーマンス

Uncontrolled Components を採用しているため、入力の変更による再レンダリングの発生を防ぎ、パフォーマンスが向上します。
補足ですが、`useForm` には [`mode`](https://react-hook-form.com/api/#useForm) オプションが存在し、
入力の変更の度にバリデーションを行う `onChange` モードを指定しても再レンダリングは発生しません（`reValidateMode` も同様）。各入力フォーム要素の参照から値の変更をサブスクライブし、バリデーションは実行されますが React の状態は更新されないためです。バリデーションが成功から失敗に切り替わるタイミング(またはその逆のタイミング)などの必要なタイミングで再レンダリングを発生させます。

公式ドキュメントから再レンダリングの回数とマウント速度の、主要なフォームバリデーションライブラリとの比較を確認することができます。

再レンダリング回数の比較：

![compare-re-renders.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83038/b89a3b29-48c6-fe5a-31e9-27a55e7c264c.jpeg)

マウント速度の比較：

![compare-mounting.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83038/2bf19697-4dca-861a-1233-64e86500b1d6.png)


## 最後に

Controlled Components と Uncontrolled Components でどちらにもトレードオフがあるため、ユースケースに応じて適切な選択をしてください！

React Hook Form には今回紹介した特徴以外にも様々な特徴が存在します。また、Material-UI や React-Select などの制御された UI コンポーネントと組み合わせて使用するための [Controller コンポーネント](https://react-hook-form.com/jp/api#Controller)や、Yup や Joi などを使用したスキーマバリデーションのための [resolver](https://github.com/react-hook-form/resolvers)、そしてフォームの状態をデバッグするための [DevTools](https://react-hook-form.com/jp/dev-tools) など様々なオプションを提供しています。

是非一度お試し頂ければと思います。

## アドベントカレンダー、明日の記事は

明日は [@takaheraw](https://qiita.com/takaheraw@github) さんの担当です！お楽しみに 🙌