如果你對 React Hooks 是什麼還沒那麼了解的話,也許可以先看看這篇的簡易教學
什麼是 useImperativeHandle
官方文件是這麼解釋的
useImperativeHandle
customizes the instance value that is exposed to parent components when usingref
.
用簡單的說法就是,你可以針對一個 react component 定義他要 expose 的任何屬性
比方說一個 <CustomButton />
可以定義一個 toggleDisabled
function 屬性讓使用這個元件的人 aka 父元件可以直接呼叫這個 function 來控制 <CustomButton />
是否 disabled
或是在 <CustomButton />
裡會記錄 button 被按了幾次並 expose 出去,父元件就能直接取得
如下 gif,在父元件 (<App />) 中可以透過 customButtonRef.current.toggleDisable
& customButtonRef.current.count
來存取 <CustomButton />
expose 出來的屬性
如何使用 useImperativeHandle
在此之前,先來看看要怎麼使用 ref 存取子元件裡的元素
如上圖有個 <CustomInput />
包裝了 input element,若想要父元件能透過 ref 存取的這個 input 則需要用到 react ref 搭配 forwardRef 來完成
然而 useImperativehandle 也是相同原理,在子元件利用 forwardRef 與 useImperativeHandle 來 expose 自訂的 ref 屬性,在父元件就能用同樣方式取得該子元件的 ref
接著來看看 useImperativeHandle 的用法
useImperativeHandle(ref, createHandle, [deps])
useImperativeHandle
should be used withforwardRef
從上方的 demo code 及官方文件應該可以很清楚的了解要如何使用,首先在元件必須透過 forwardRef 拿到 ref object 並塞入 useImperativeHandle 裡,然後定義要 expose 哪些屬性,以及用過 Hooks 一定會碰到的 dependency array
使用情境
然而在什麼情況下會用到這個 Hook 呢?
試想你有一個發送 email 的系統,其中一頁可以新增模板,包含 主旨
、收件人
、副本
、內文
或其他更多欄位,按下儲存後要把這些欄位的值送給 server
而有另一個發送頁面,有著同樣的 email 編輯器介面與欄位,使用者輸入完或直接選擇模板後可以按下發送,但寄送前會在前端針對各欄位做驗證再把值送給 server
在這種狀況下勢必會把 email editor 的部分拆成共用元件 (只會包含相關欄位的部分,並不會有 submit button),但抉擇的時候就來了,要怎麼管理這些欄位的 state
1. 不管了,都給父元件處理,我只管 UI
於是有了這樣的 code
可以看到一個欄位就會有一個 state、prop、handler,那如果今天欄位更多了,要用到的頁面也更多,那是不是 EmailEditor
就變得沒那麼好用了
2. 那把欄位 state 包起來統一處理不就好了
於是 code 變成了這樣
變得正常且精簡許多,唯一的問題是在使用者輸入的過程中會讓父元件一直 re-render,需要注意是否會有相關影響
再者,其實父元件並不在意使用者輸入的過程,而是當按下儲存/送出時要拿到每個欄位的值
3. Email Editor 自己管理欄位 state,並 expose 出去
於是 useImperativeHandle 終於出場
首先中間欄的 EmailEditor
自己管理自己的 state,再透過 useImperativeHandle
釋出一個 values
屬性讓父元件可以存取到各欄位的值
而左欄 App
即是父元件,把 emailEditorRef
綁到 EmailEditor
上即可使用 emailEditorRef.current.values
拿到當前欄位的值,也就如同右欄輸入完欄位後按下 Send Email
button 可在父元件拿到正確的值接著作後續的處理
以上,即是對 useImperativeHandle 的一點小了解,雖然操作上跟 react 資料流有點衝突,但也是有他好用之處