🏏 素振り: React Hook Form
あーはいはい、React Hook Formね、知ってる知ってる(知らない)
そんな状態なので素振りしておく。
React Hook Form
https://react-hook-form.com/get-started
React Hook Form の重要なコンセプトの一つは、非制御コンポーネント (Uncontrolled Components) をフックに登録(
register) し、フォームフィールドの値を検証と収集できるようにすることです。
DOMベースに値を持つコンポーネントを主体に、いい感じにフォーム管理ができるものという理解をした。 自分がReactを書くときは今のところ制御コンポーネントを使うケースが多いので、React Hook Form 向けに脳をスイッチしないといけなさそう。
useForm と register
特に重要なのは useForm と register の2つ
const { register, handleSubmit } = useForm();
<input {...register("firstName")} />
register から UseFormRegisterReturn 型の値が返ってくる。
非制御コンポーネントで必要になる ref や、onChange, onBlur なんかも一通り含まれるので、これをprops適用するだけで一通り input タグのセットアップが終わる。
既存フォームへ適用する際は、register そのものを渡すか、 forwardRef で ref を透過的に扱う。
❓ 状態管理する場合の例として、状態管理への書き込みをonSubmit に仕込んでる。現実問題他のタイミングのほうが多そうだけど、値をサッを全部取ったりはできない?
→ getValues 呼べばok
TypeScript との組み合わせ
https://react-hook-form.com/get-started#TypeScript
useForm に対して型定義を指定するだけだった。
getValues とかもいい感じに型がついた。
バリデーション
❓ バリデーションといえばzodを思い浮かべたけど、連携できるのか?
人類が考えることは同じだった。先駆者の皆様ありがとう
react-hook-form と zod でバリデーションのその先へ React Hook FormとZodを組み合わせて利用する|食べログ フロントエンドエンジニアブログ|note
どうやら Resolver という仕組みがあり、バリデーションライブラリであればシームレスに統合できるらしい。
https://react-hook-form.com/api/useform/#resolver https://www.npmjs.com/package/@hookform/resolvers
zod であれば @hookform/resolvers/zod でいける。
FormData と型定義が重複してツラいのかと思ったけど、 z.infer で型に変換すればいいだけだった。
https://github.com/colinhacks/zod#type-inference
const schema = z.object({
firstName: z.string().min(1),
gender: z.enum(["female", "male", "other"]),
});
type FormData = z.infer<typeof schema>;
<input {...register("firstName")} />
{errors.firstName?.message && <p>{errors.firstName?.message}</p>}
完全に理解したので、ほかのAPIとかをみてみる。
useForm
register 以外で気になったやつを見てみる
formState
フォームの状態に関する色々を含んだ state らしい。
- isDirty で変更されてるか / dirtyFields で変更されたフィールド値
- defaultValues で初期値
- valid かどうか
みたいなものが取れる。
watch
変更に応じて再描画をトリガーさせたい時に使えるらしい。 なんか迂闊に使うとめっちゃパフォーマンスへの影響がある気配を感じた。
reset
リセットするらしい。 コンポーネントのライフサイクル的にリセットが必要なケースでは役に立ちそう。
trigger
バリデーションを発火させるらしい。メソッド名 validate とかじゃないのね
useController
他のフォームコンポーネントライブラリを使う時に、register だと適用できないので、useController を使うことで独自にマッピングできるぽい。
基本的に register を使っておいて、どうしようもないときの手段と理解した。
useFormContext
コンポーネントってフォームも含めてネストするのは容易に発生するので、その時に Context 使っていい感じに子孫に受け渡せる。便利そう。
実際には FormProvider でラップする。
でもテスト書く時とかどうなるんだろ。
useWatch
watch での再描画のスコープをフックの範囲に閉じ込めるものぽい。
迂闊に watch 使うと再描画激して死ぬみたいなシチュエーションで、子コンポーネントだけに再描画範囲を閉じ込めたりできそう。ナルホド
useFormState
useForm で生成される状態のうち、一部の状態のみに絞り込んでSubscribeできるようになる。
FirstNameInput みたいなコンポーネントを作った時に、 useFormState 経由で firstNameだけ購読させれば、レンダリングの抑制ができるぽい。
useFieldArray
配列系のフォームの操作で使える。 みんな大好き ToDo アプリの行操作とかを作ろうと思ったら使いそう。 普段の開発でも登場シチュエーションは多そうな気配を感じた。
所感
便利そう。導入されるケースが多い理由が少し理解できた気がする。 APIも多すぎず、かつシンプルで理解しやすかった。
TypeScriptとの親和性もあって、zodでバリデーション組んだりできるのも良さそうだった。フォームの初期化とかもサクっとできるのはよさそう。
一方で、 useController とかでUIコンポーネントと繋ぎこみとかし始めると結構カオスになっていきそうな気配も感じた。なんでもかんでも便利そうだからと言って突っ込むと死ぬのはお約束なので、まあ自己責任ではありそう。