估計都比較熟悉這些HOOKS了吧:useState
, useEffect
, useContext
, useMemo
。但我當第一次看到useImperativeHandle
時,一臉懵逼(這是什麼鬼東西~~~)。
一、是什麼?
React官網對useImperativeHandle
介紹也比較簡短。總結一句話就是:子元件利用useImperativeHandle
可以讓父元件輸出任意資料。
// FancyInput元件作為子元件
const FancyInput = React.forwardRef(function FancyInput(props, ref) {
const inputRef = useRef();
// 命令式的給`ref.current`賦值個物件
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus()
}
}));
return <input ref={inputRef} ... />
})
// Example元件作為父元件
function Example() {
const fancyInputRef = useRef()
const focus = () => {
fancyInputRef.current.focus()
}
return (
<>
<FancyInput ref={fancyInputRef} />
</>
)
}
二、怎麼用?
2.1 語法
useImperativeHandle(ref, createHandle, [deps])
ref
需要被賦值的ref
物件。createHandle
:createHandle
函式的返回值作為ref.current
的值。[deps]
依賴陣列,依賴發生變化會重新執行createHandle
函式。
2.2 進階:什麼時候執行createHandle
函式?
測試發現和useLayoutEffect
執行時機一致。
修改下元件FancyInput
內容:
const FancyInput = React.forwardRef(function FancyInput(props, ref) {
const inputRef = useRef();
console.log('render 1')
useLayoutEffect(() => {
console.log('useEffect1', ref)
})
useImperativeHandle(ref, function() {
debugger
console.log('useImperativeHandle')
return {
focus: () => {
inputRef.current.focus();
}
}
})
useLayoutEffect(() => {
console.log('useEffect2', ref);
})
console.log('render 2')
return <input ref={inputRef} placeholder="FancyInput"/>;
})
看看控制檯輸出發現createHandle
函式的執行時機和useLayoutEffect
一致,這樣就保證了在任意位置的useEffect
裡都能拿到最新的ref.current
的值。
注意:執行createHandle
函式的還有個前提條件,即useImperativeHandle
的第一個實參ref
必須有值(否則執行createHandle
函式也沒意義啊)。
2.3 應用場景
目前專案已經有多處使用場景了,主要是解決父元件獲取子元件的資料或者呼叫子元件的裡宣告的函式。
如formik庫的一處使用:
React.useImperativeHandle(innerRef, () => formikbag);
2.4 最佳實踐
React官網裡給出了幾點使用建議:
- 儘量避免命令式地給
ref.current
賦值,儘量採用宣告式的(即讓React內部處理); - 和
forwardRef
搭配使用
這個不一定,比如上面fomik庫就沒有這樣做。
三、原理
先回顧下我們之前是如何使用ref
的:
- 期初利用
ref
訪問子元件的例項或則DOM元素; - 後來
useRef
出現了,我們在函式元件裡利用useRef
還可以儲存一些類似成員變數的資料。
再回顧下React如何處理宣告式ref的:
React will assign the current property with the DOM element when the component mounts, and assign it back to null when it unmounts.
通過之前的知識我們可以達成幾點共識:
- 給
ref.current
賦值是個副作用,所以一般在Did
函式或者事件處理函式裡給ref.current
賦值; - 元件在解除安裝時要清理
ref.current
的值。
本質上useImperativeHandle
就是在幫我們做這些事情。
四、為什麼需要useImperativeHandle
我們都知道父元件可以利用ref
可以訪問子元件例項或者DOM元素,這其實相當於子元件向父元件輸出本身例項或者DOM元素。而利用useImperativeHandle
子元件可以向父元件輸出任意資料。