watch vs useWatch in react hook form

watch 與 useWatch 在 react hook form 中的差異

前言

會有本篇文章是因為同學在課程中問起 watchuseWatch 的差異,第一次回答的時候,簡略看了一下文件事後才發現先前的回答誤導到了同學,因此才寫這篇文章詳細的記錄下它們的差異,希望不再犯相同錯誤 😐。

watch 用途與實際案例?

監聽特定的輸入並回傳它們的值。它在渲染輸入值時很有用,也可以用來依據條件來決定要渲染什麼。

根據官方的簡略說法就是:「監聽表單變化,且在變化時執行某些動作」,那麼實際案例上什麼時候會需要用到這個方法?除了官方有提供實際案例🔗之外,我也會在這裡舉兩個官方提供的用法並實際寫一次看看~

案例一:選擇付費方式,顯示對應的付款欄位

這時候就可以用到 watch 來監聽 paymentMethod 值的變化,當使用貨到付款就使用貨到付款的欄位、當使用信用卡付款時就顯示信用卡付款的欄位、當使用轉帳付款就顯示轉帳的付款的欄位……依此類推。

const { register, handleSubmit, watch } = useForm({
defaultValues: {
paymentMethod: 'cashOnDelivery',
},
});
// 監聽表單中 hasCreditCard 的值,並解構出來使用
const [paymentMethod] = watch(['paymentMethod']);
return (
<form onSubmit={handleSubmit((data) => console.log(data))}>
<label>
付款方式:
<select {...register('paymentMethod')}>
<option value="cashOnDelivery">貨到付款</option>
<option value="creditCard">信用卡</option>
</select>
</label>
{paymentMethod === 'creditCard' && <div>信用卡支付細節……</div>}
{paymentMethod === 'cashOnDelivery' && <div>貨到付款支付細節……</div>}
</form>
);

除了以上的案例監聽單一欄位外,像以下範例監聽多個欄位都是沒問題的。

const watchAllFields = watch(); // 當不傳入參數時,監聽一切動靜
const watchFields = watch(['firstName', 'lastName']); // 指定特定要監聽的值

watch 實際上就像是幫我們把 useStateonChange 的組合給做好了,只要傳入要監聽的欄位資料,就可以監聽到表單的變化,並且可以在變化時執行某些動作,這樣的方法在複雜的表單中可以讓代碼更為精簡易懂。

// 傳統方法製作一個普通的 Controlled Form
const [name, setName] = useState('');
<input type="text" value={name} onChange={(e) => setName(e.target.value)} />;
渲染次數:2

案例二:監聽但不觸發重新渲染

但如果你只是想要監聽表單的變化,但不想要觸發重新渲染,就可以使用 watch 搭配 useEffect 來監聽表單的變化,但不觸發重新渲染。

useEffect(() => {
const subscription = watch((data) => {
console.log(data);
});
return () => subscription.unsubscribe();
}, [watch]);

必須特別注意如果 watch 有回呼函式的話,會需要使用到 useEffect 的 cleanUp 函式來取消訂閱

useWatch 用途與實際案例?

基本與 watch 相似,差別在於會獨立渲染於該 Hook 的層級,並有機會提升應用程式的效能。

前面提到 watch 使用起來就像是 useStateonChange 的組合,只要表單的值有變化就會觸發該 useForm 所在的層級重新渲染,只要表單一複雜,就會碰上效能上的瓶頸

因此 useWatch 就是應對這個問題而生,只要監聽的值有更動它會獨立渲染該 useWatch 的層級,而不是整個表單的根元件,以下是實際範例:

// 定義在元件之外的變數,用於紀錄渲染次數
let childRender = 0;
let parentRender = 0;
// 子元件,使用 useWatch 監聽表單中 lastName 的值
function Child({ control }) {
useWatch({
control,
name: 'lastName',
});
childRender += 1;
return <p>子元件渲染次數: {childRender}</p>;
}
// 父元件,使用 useWatch 監聽表單中 firstName 的值
function Parent() {
const { register, control } = useForm();
useWatch({
name: 'firstName',
control,
});
parentRender += 1;
return (
<form>
<input {...register('firstName')} placeholder="firstName" />
<input {...register('lastName')} placeholder="lastName" />
<p>父元件渲染次數: {parentRender}</p>
<FirstNameWatched control={control} />
</form>
);
}

父元件渲染次數: 2

子元件渲染次數: 1

在範例中我們在哪個元件裡面使用 useWatch 它就會從哪裡開始重新渲染,因此就算子元件的值更動了也不會讓父元件重新渲染,這樣就可以提升效能。

總結

useWatchwatch 差別僅在他們重新渲染的層級,如果程式本身沒有效能問題使用 watch 就足矣,但如果有效能問題,就可以使用 useWatch 來解決。

參考資料