getValuesとwatchとuseWatachの使い分け
react-hook-formでは、formの値を取得するAPIが3種類提供されている
useFormの返り値としてwatch()とgetValues()
他のhooksとして、useWatch
これらの差異は何で、どういう基準で使い分ければよいのか?
結論
パフォーマンスの観点(renderingを引き起こすかどうか)で差異がある
使い勝手はどれもほぼ同じ
検討の順序としては、以下の順序で見ると良い
getValues()→useWatch()→watch()
パフォーマンス的にはgetValues()が最も良い
3つのAPIの基本概念
useFormのgetValues()
docs
実行時に、再renderingを引き起こさない
入力の変更をsubscribeしない
ただ値を取得したいときはこれを使えばいい
例えば、「このボタンをクリックした時にフォームの値が欲しい」とか
getValues以外のことが原因で再renderingされた時に初めて値が更新される
useWatch
docs
rootではなく、呼び出した箇所で再renderingが起きる
実行順序が重要
custom hooksでかつ、getValuesで無理ならこれを使うのが良い
useFormのwatch()
docs
formのrootで、再reneringを引き起こす
formの値が更新されると、renderingを引き起こす
This API will trigger re-render at the root of your app or form, consider using a callback or the useWatch api if you are experiencing performance issues.
useWatchの方はわかった
useCallbackの方はどうだろう #??
watch result is optimised for render phase instead of useEffect's deps, to detect value update you may want to use an external custom hook for value comparison.
useWatchよりも、watchを使うべき状況ってあるのか #??
useFormを使っている箇所と、watchを使っている箇所が同一のcomponentなら、useWatch/watchのいずれを使っても全く同じ挙動になるはず
でもそれだと、watch消して、useWatchの使用を強制させても良い気もすけど
ほとんど使うことない気がしているmrsekut.icon
getValues()だと困るケース
例えば、普通にsetValues()で値を更新したときは、再rendringは引き起こされない
setValuesとrenderringにも条件があってちょっとややこしいがmrsekut.icon
だから、例えばこんなhooksを作った時に
code:ts
const useItemForm = () => {
const { setValue, getValues } = useFormContext<{ items: Item[] }>();
const items = getValues('items');
const addItem = (item: Item) => {
setValue('items', ...items, item);
};
return { items, addItem };
};
addItemでアイテムを追加できるが、追加した際に、renderingは引き起こされない(setValueの条件による)
そのため、itemsを購読し続けても、画面上にはその変更が反映されない
上のコードみたいに、getValues()をhooksやcomponentの第1階層目に書くことはほとんどないと思うmrsekut.icon
使うとしたら以下のように、何かしらのhandlerの中で値を取得する時に使う
code:ts
const useCommentForm = () => {
...
const insertReplyUsername = useCallback(
(username: string) => {
const comment = getValues('comment');
setValue('comment', ${comment} @${username} );
},
getValues, setValue
);
return {..}
}
感覚的には、ReacoilのuseRecoilCallbackと近いmrsekut.icon
watch()かuseWatchか
watch()とuseWatchの違いは、再renderingを引き起こす場所
watch()だと、root(根本のuseFormを呼び出している箇所)から再renderingされる
useFormContextを使った場合もそうなる
例えば、こういう構造のとき、
code:ts
const Root = () => {
const methods = useForm(..)
// ★ここがrenderingされるか?
return (
<FormProvider {...methods}>
<ChildForm1 />
<ChildForm2 />
</FormProvider>
);
}
const ChildForm1 = () => {
// ① watchを使った場合
const {watch} = useFormContext();
const items = watch('items')
// ② useWatchを使った場合
// const items = useWach({name: 'items'})
// ★ここがrenderingされるか?
return (..)
}
ChildForm1の中でformの値であるitemsを見たい時に、watchを使うか、useWatchを使うかで
再renderingが引き起こされる場所が異なってくる
watchを使った場合
Rootが再renderingされる
そのため、ChildForm1とは関係ないはずの、ChildForm2も再renderingされてしまう
一方で、useWatchを使った場合は、
Rootは再renderingされず、
ChildForm1の中でのみ、再renderingされる
https://react-hook-form.com/faqs#watchvsgetValuesvsstate