前言
會有本篇文章是因為同學在課程中問起 watch
與 useWatch
的差異,第一次回答的時候,簡略看了一下文件事後才發現先前的回答誤導到了同學,因此才寫這篇文章詳細的記錄下它們的差異,希望不再犯相同錯誤 😐。
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
實際上就像是幫我們把 useState
和 onChange
的組合給做好了,只要傳入要監聽的欄位資料,就可以監聽到表單的變化,並且可以在變化時執行某些動作,這樣的方法在複雜的表單中可以讓代碼更為精簡易懂。
// 傳統方法製作一個普通的 Controlled Formconst [name, setName] = useState('');<input type="text" value={name} onChange={(e) => setName(e.target.value)} />;
案例二:監聽但不觸發重新渲染
但如果你只是想要監聽表單的變化,但不想要觸發重新渲染,就可以使用 watch
搭配 useEffect
來監聽表單的變化,但不觸發重新渲染。
useEffect(() => { const subscription = watch((data) => { console.log(data); }); return () => subscription.unsubscribe();}, [watch]);
必須特別注意如果 watch
有回呼函式的話,會需要使用到 useEffect
的 cleanUp 函式來取消訂閱
useWatch
用途與實際案例?
基本與 watch
相似,差別在於會獨立渲染於該 Hook 的層級,並有機會提升應用程式的效能。
前面提到 watch
使用起來就像是 useState
與 onChange
的組合,只要表單的值有變化就會觸發該 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> );}
在範例中我們在哪個元件裡面使用 useWatch
它就會從哪裡開始重新渲染,因此就算子元件的值更動了也不會讓父元件重新渲染,這樣就可以提升效能。
總結
useWatch
與 watch
差別僅在他們重新渲染的層級,如果程式本身沒有效能問題使用 watch
就足矣,但如果有效能問題,就可以使用 useWatch
來解決。