🏏 素振り: 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コンポーネントと繋ぎこみとかし始めると結構カオスになっていきそうな気配も感じた。なんでもかんでも便利そうだからと言って突っ込むと死ぬのはお約束なので、まあ自己責任ではありそう。