useImperativeHandle應(yīng)用及原理
OOKS了吧:useState,?useEffect,?useContext,?useMemo。但我當(dāng)?shù)谝淮慰吹?code style="box-sizing: border-box;font-family: var(--bs-font-monospace);font-size: 0.875em;direction: ltr;unicode-bidi: bidi-override;color: rgb(214, 51, 132);overflow-wrap: break-word;">useImperativeHandle時(shí),一臉懵逼(這是什么鬼東西~~~)。
一、是什么?
React官網(wǎng)對(duì)useImperativeHandle介紹也比較簡短。總結(jié)一句話就是:子組件利用useImperativeHandle可以讓父組件輸出任意數(shù)據(jù)。
// FancyInput組件作為子組件
const FancyInput = React.forwardRef(function FancyInput(props, ref) {
const inputRef = useRef();
// 命令式的給`ref.current`賦值個(gè)對(duì)象
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對(duì)象。createHandle:createHandle函數(shù)的返回值作為ref.current的值。[deps]
依賴數(shù)組,依賴發(fā)生變化會(huì)重新執(zhí)行createHandle函數(shù)。
2.2 進(jìn)階:什么時(shí)候執(zhí)行createHandle函數(shù)?
測(cè)試發(fā)現(xiàn)和useLayoutEffect執(zhí)行時(shí)機(jī)一致。
修改下組件FancyInput內(nèi)容:
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"/>;
})

看看控制臺(tái)輸出發(fā)現(xiàn)createHandle函數(shù)的執(zhí)行時(shí)機(jī)和useLayoutEffect一致,這樣就保證了在任意位置的useEffect里都能拿到最新的ref.current的值。
注意:執(zhí)行createHandle函數(shù)的還有個(gè)前提條件,即useImperativeHandle的第一個(gè)實(shí)參ref必須有值(否則執(zhí)行createHandle函數(shù)也沒意義啊)。
2.3 應(yīng)用場(chǎng)景
目前項(xiàng)目已經(jīng)有多處使用場(chǎng)景了,主要是解決父組件獲取子組件的數(shù)據(jù)或者調(diào)用子組件的里聲明的函數(shù)。
如formik庫的一處使用:
React.useImperativeHandle(innerRef, () => formikbag);
2.4 最佳實(shí)踐
React官網(wǎng)里給出了幾點(diǎn)使用建議:
盡量避免命令式地給
ref.current賦值,盡量采用聲明式的(即讓React內(nèi)部處理);和
forwardRef搭配使用
這個(gè)不一定,比如上面fomik庫就沒有這樣做。
三、原理
先回顧下我們之前是如何使用ref的:
期初利用
ref訪問子組件的實(shí)例或則DOM元素;后來
useRef出現(xiàn)了,我們?cè)诤瘮?shù)組件里利用useRef還可以存儲(chǔ)一些類似成員變量的數(shù)據(jù)。
再回顧下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.
通過之前的知識(shí)我們可以達(dá)成幾點(diǎn)共識(shí):
給
ref.current賦值是個(gè)副作用,所以一般在Did函數(shù)或者事件處理函數(shù)里給ref.current賦值;組件在卸載時(shí)要清理
ref.current的值。
本質(zhì)上useImperativeHandle就是在幫我們做這些事情。
四、為什么需要useImperativeHandle
我們都知道父組件可以利用ref可以訪問子組件實(shí)例或者DOM元素,這其實(shí)相當(dāng)于子組件向父組件輸出本身實(shí)例或者DOM元素。而利用useImperativeHandle子組件可以向父組件輸出任意數(shù)據(jù)。
