以下の内容はhttps://let.blog.jp/tag/react-hook-formより取得しました。


react-hook-form の感想
便利そうかもと思ったらやっぱり違うなと思ったりで ちゃんと使うことはなかったライブラリですが 使われてるコードをいじる機会があったので色々使ってみた感想です
簡単に言うと React ぽくなくなったり 単純なフォームじゃなくなると useEffect だらけで辛くなるので そんなに積極的に使いたいものではないです

react-hook-form を使うと 内部で state を管理してくれて 再レンダリングを防げます
各 input が props ではなくコンテキスト内の form から値を取得するので 親の再レンダリングが不要になり 変更があった input のコンポーネント内だけが再レンダリングされます
表示内容が多いページで memo なしだと入力ごとに再レンダリングされる範囲が広くなり 重たくなったりしますが それを防げます

フォームの制御が特になく 項目が多いだけのフォームだと楽に使えて便利です
しかし 入力値の変化に応じてあちこちを変更する場合は面倒になってきます

useEffect(() => {
const subscription = watch((values, { name, type }) => {
if (name === "foo") {
setValue("bar", values.foo)
} else if (name === "bar") {
setState(values.bar)
}
})
return () => subscription .unsubscribe()
}, [watch])

みたいに callback を指定する watch を使い form の値や state を更新していくことになります
useEffect が必要です

更新された場所の名前はわかりますが 更新前の値はわかりません
変化した内容に応じて処理を変えたいなら自分で前の値を保持する仕組みを作っておかないといけません

また イベントハンドラ的な部分での処理ではなく render 関数中の処理で現在の form の値を使いたいなら 名前指定で watch を使います

const [foo, bar] = watch(["foo", "bar"])

この場合はどれが変わったのかわかりません
また 変わるたびに この watch を使ったコンポーネントが再レンダリングされるので useState に入れているのと変わらなくなります
ものによっては form の値のほとんどを watch に入れることになり useState と対して変わらないってこともあります

watch で取得した値を使うのが hook だと仕方ないですが JSX 部分なら input 以外でも form を参照するようにしてしまうのもありです
input のように controller を使って値を取得すれば watch にする必要がなく コンポーネント全体の再レンダリングを減らせます

input に使う以外の form とは関係ない値でも form に入れてしまい state にせず form で管理するのもありかもです
そうすれば form の処理だけに統一できますし 名前指定の watch を減らせます
それに コンポーネント間の共有でコンテキストのようにも使えます
親コンポーネントで form に関数を入れて 深い部分のコンポーネントでそれを参照します
ただしあくまでライブラリとしては form なのでこの使い方はいいのだろうか?という気はします
想定されたことを外れると将来的なアップデートで面倒な目に合うのは十分考えられます
そういう使い方のものを別に用意したほうがいいのかもという気はしてます

深い部分で watch を使う注意点ですが 使ったコンポーネントだけが再レンダリングされそうに見えてしまいますがそうではないです
React の仕組み的にそういう手段はなく useForm を使ったコンポーネントが再レンダリングされます
その結果 その子孫コンポーネントも再レンダリングされてそうみえるだけです

こういうことをしてみるとわかりやすいです

const Input = ({ control, name }) => {
const { field } = useController({ control, name })
return <input {...field} />
}

const Child = ({ control, watch, name }) => {
const value = watch(name)
console.log("render Child", name)
return (
<div>
<div>input value: {value}</div>
<Input control={control} name={name} />
</div>
)
}

const MemoChild = React.memo(Child)

const Parent = () => {
const { watch, control } = useForm({ defaultValues: { a: "1", b: "2", c: "3" } })
console.log("render Parent")
return (
<div>
<Input control={control} name="a" />
<hr />
<Child control={control} name="b" watch={watch} />
<hr />
<MemoChild control={control} name="c" watch={watch} />
</div>
)
}

Parent で useForm して watch を Child に渡します
Child で watch していて その input を書き換えると 「render Parent」 もログに表示されます
Parent コンポーネントも再レンダリングされています
また Child をメモした版の MemoChild を使うと props の control, name, watch には変更がないため MemoChild コンポーネントは再レンダリングされません
結果 Parent は再レンダリングされるのに MemoChild は再レンダリングされず 「render Child c」 はログに表示されません
画面上でも input の現在の状態を表示しているのですが ここが更新されず初期状態のままです

また props や state を見て ちょっと加工するというだけでも useEffect で setValue が必要になります
普通の React なら render 関数内で計算したり JSX 内に式を入れるくらいで済むことのために useEffect が増えていくのは見通しが悪くなるので不満です

さらに問題になるのはあちこちで同じ値を更新したいときです
useEffect やイベントハンドラの中で setValue することになりますが 特に useEffect だと呼び出し順がわかりづらいです
あちこちのコンポーネントから setValue されるとどういう順で呼び出されて値が変わるのかデバッグしづらく 思い通り動かないことがけっこうありました
通常の React なら useEffect は極力なくせて render 関数内での処理です
親から子の順ですし レンダリングごとにイミュータブルのはずなので 困ることは少ないです

完全に独立した入力項目で 初期値以外は外部から変更されることがないような場合はシンプルに書けて良さそうですが 制御する部分が多いと不満点が目立ちます
やっぱり 素の React で state 管理してる方が見やすくていい気がしますね
react-hook-form は変更時の処理をまとめて行えない?
react-hook-form のドキュメント見ていて useForm に onChange がないことに気づいた
個別の onChange でやれってことかもしれないけど 相互に関連する入力が多くて連鎖して変化する場合にそれは使いづらい
A を変更したら B も変わって B と C が空欄になったら D もクリアして みたいなルール
form 全体の onChange イベントでまとめて処理したい

渡せる物を探すと resolver というのがあって values を受け取って values と errors を返す関数を設定できるみたい
これがまとめて変更管理するのを兼ねてるのかと思ったけど values に修正済みを渡しても form の値に変化なし
バリデーション用らしいし 実際の入力値に影響は与えられないみたい

自分で作ってる簡単なフォーム制御するものだと逆に reducer 風にまとめて扱うもののみだから この制限があるのはかなりつらめ
そういう方針のものなんだろうと諦めたところで watch を発見
変更時に再レンダリングするためのものって思ってたけど 再レンダリングなしでコールバック関数を呼び出してもらう使い方もできた

const Component = () => {
const { register, watch, setValue } = useForm()

watch((values, { name, type }) => {
console.log(values, name, type)
const set = (k, v) => {
if (values[k] === v) return
setValue(k, v)
}

if (name === "A") {
set("B", values.A)
}
if (name === "B" || name === "C") {
if (values.B === "" && values.C === "") {
set("D", "")
}
}
})

// ...
}

setValue を使った変更でも watch のコールバックが呼び出されたので 連鎖して変更するときにユーザーの入力で呼び出されたときに一度にまとめて setValue せず ひとつずつ setValue して毎回 name を見ての判定にもできる(無限ループ注意)
A が変われば B も変わり B が変われば C も変わるときに A の変更を検知したら B と C を一度に書き換えず B だけ変える
B が変わればまたコールバックが呼び出されて B の変更なので C を変える という感じ



(注)
この例だと手抜きで省略してるけど コールバック登録型の watch は useEfffect の中でしないとだめ
再レンダリング時にリスナが上書きされたりはせず毎回追加で登録される
レンダリングのたびコールバック関数が呼び出される回数が増えて重くなる
useEffect で初回だけ実行するようにしないといけない
外部の変数を使うために useEffect の依存配列を使うなら 複数回 watch を呼び出すことになるので古い watch の解除が必要
watch の返り値を受け取って useEffect が返す関数の中でサブスクリプションの解除をする



以上の内容はhttps://let.blog.jp/tag/react-hook-formより取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14