個人的にReact Hook Formで気をつけていること
はじめに
React Hook Formの特徴や個人的に気をつけていることをまとめました💫
React Hook Formの特徴
- レンダリング回数を減らすことができる
- バンドルサイズの軽減が可能
レンダリングについて
通常のReactでは、state
で値を管理し、state
を更新(setState
)するたびに再レンダリングが発生します。
一方、React Hook Formでは、値の管理にstate
ではなくref
を使用するため、再レンダリングを抑制できます。
この仕組みにより、フォーム操作時に不要な再レンダリングを減らすことが可能です。
詳細は公式ドキュメントにも記載されています。
-
state
で値を管理する方法はControlled Componentsと呼ばれます。 - 一方、
ref
で値を管理する方法はUncontrolled Componentsと呼ばれます。
Type | Controlled Components | Uncontrolled Components |
---|---|---|
管理方法 |
state (React)での管理 |
ref (DOM)での管理 |
レンダリング | 値が変化するたびに再レンダリングが発生する | 値が変化しても再レンダリングが発生しない |
個人的にReact Hook Formで気をつけているポイント
1. 値を取得する際のレンダリングコスト
簡単なフォーム(ログインフォームなど)であれば問題ないのですが、フォームの値を取得して何か処理をしたい場合には、少し複雑になります。
React Hook Formで値を取得する方法には、getValues
、watch
、useWatch
の3種類があります。
種類 | getValues | useWatch | watch |
---|---|---|---|
再レンダリング | 発生しない | コンポーネント単位で発生 | フォーム全体で発生 |
使用タイミング | 値を単に取得する場合 | 値の取得+UI/UX更新 | 値の取得+UI/UX更新 |
-
getValues
は再レンダリングを発生させないため、UI/UXの更新(たとえばエラーメッセージの表示など)には使用できません。 -
watch
はフォーム全体に再レンダリングを発生させます。 - 一方、
useWatch
はコンポーネント単位で再レンダリングを発生させるため、watch
よりも影響範囲が狭いです。
しかし、useFormContext
でコンポーネント化せずに、直接フォーム配下にuseWatch
を記述してしまうと、watch
と同様にフォーム全体を再レンダリングしてしまいます。
export default function App() {
const { register, handleSubmit, control } = useForm({ mode: "onBlur" });
const onSubmit = (data) => console.log(data);
const useWatchValue = useWatch({ control, name: "useWatch" });
return (
<form onSubmit={handleSubmit(onSubmit)}>
{/* これだと意味がなく、フォーム全体が再レンダリングされてしまう */}
<input {...register("useWatch")} />
{useWatchValue}
<button type="submit">Submit</button>
</form>
);
}
このように、React Hook Formの仕様を理解していないと、強みである「再レンダリングの抑制」の効果が薄れてしまいます。
2. Controllerの使用
外部コンポーネント(MUIなど)を使用する際、Controller
でコンポーネントをラップするのが一般的です。
ただ、Controller
でラップされたコンポーネントはControlled Components同様に再レンダリングが発生します。
本来、Uncontrolled Components
の利点としてレンダリングの抑制を活かせるはずですが、Controller
を使うことでその効果が損なわれてしまいます。
では、なぜController
でラップする必要があるのでしょうか。
これは、多くの外部コンポーネントがReactのref
を直接受け取る仕組みに対応していないためです。そのため、Controller
を利用して、フォームの制御を行う必要が出てきます。
React公式ドキュメントでは、ref
に関して次のように説明しています。
独自のコンポーネント、例えば
<MyInput />
に ref を置こうとすると、デフォルトでは null が返されます。以下はそれを示す例です。ボタンをクリックしても入力フィールドにフォーカスが当たらないことに注意してください。
これは、デフォルトでは React は、コンポーネントが他のコンポーネントの DOM ノードにアクセスできないようにしているためです。自分自身の子でさえもです! これは意図的なものです。ただでさえ ref は控えめに使うべき避難ハッチ (escape hatch) です。別のコンポーネントの DOM ノードまで手動で操作できてしまうと、コードがさらに壊れやすくなってしまいます。
代わりに、内部の DOM ノードを意図的に公開したいコンポーネントは、そのことを明示的に許可する必要があります。コンポーネントは、自身が受け取った ref を子のいずれかに「転送 (forward)」するよう指定できます。MyInput が forwardRef API を使ってこれをどのように行うのか見てみましょう。
このように、独自のコンポーネントにref
を割り当てるにはforwardRef
を使用する必要があると明記されています。
ただ、外部コンポーネントはforwardRef
に対応していないことが多いので、Controller
でラップする必要があるということだと思います。
(MUIは割とforwardRefに対応してるコンポーネントが多いからController
を使用しなくてもいけそう?)
まとめ
フォームが複雑になったり外部コンポーネントを使用したりする場合は、再レンダリングの影響を受けやすくなります。
利用する際には仕様をしっかり理解し、意図した通りにパフォーマンスを引き出せるようにしましょう💫
参考記事
Discussion