最陌生的hooks: useImperativeHandle

普拉斯強發表於2021-09-30

估計都比較熟悉這些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])
  1. ref
    需要被賦值的ref物件。
  2. createHandle
    createHandle函式的返回值作為ref.current的值。
  3. [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"/>;
})

image.png
看看控制檯輸出發現createHandle函式的執行時機和useLayoutEffect一致,這樣就保證了在任意位置的useEffect裡都能拿到最新的ref.current的值

注意:執行createHandle函式的還有個前提條件,即useImperativeHandle的第一個實參ref必須有值(否則執行createHandle函式也沒意義啊)。

2.3 應用場景

目前專案已經有多處使用場景了,主要是解決父元件獲取子元件的資料或者呼叫子元件的裡宣告的函式。
formik庫的一處使用:

React.useImperativeHandle(innerRef, () => formikbag);

2.4 最佳實踐

React官網裡給出了幾點使用建議:

  1. 儘量避免命令式地給ref.current賦值,儘量採用宣告式的(即讓React內部處理);
  2. forwardRef搭配使用
    這個不一定,比如上面fomik庫就沒有這樣做。

三、原理

先回顧下我們之前是如何使用ref的:

  1. 期初利用ref訪問子元件的例項或則DOM元素;
  2. 後來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.

通過之前的知識我們可以達成幾點共識:

  1. ref.current賦值是個副作用,所以一般在Did函式或者事件處理函式裡給ref.current賦值;
  2. 元件在解除安裝時要清理ref.current的值。

本質上useImperativeHandle就是在幫我們做這些事情。

四、為什麼需要useImperativeHandle

我們都知道父元件可以利用ref可以訪問子元件例項或者DOM元素,這其實相當於子元件向父元件輸出本身例項或者DOM元素。而利用useImperativeHandle子元件可以向父元件輸出任意資料。

整理自GitHub筆記:useImperativeHandle

相關文章