React函數式組件使用Ref


目錄:

  1. 簡介
  2. useRef
  3. forwardRef
  4. useImperativeHandle
  5. 回調Ref

簡介

大家都知道React中的ref屬性可以幫助我們獲取子組件的實例或者Dom對象,進而對子組件進行修改,是一個很方便的特性。在傳統類組件中,我們通過使用 React.createRef() 創建的,並通過 ref 屬性附加到 React 元素來使用。而隨着hooks的越來越廣泛的使用,我們有必要了解一下在函數式組件中,如何使用Ref.
想要在函數式組件中使用Ref,我們必須先了解兩個Api,useRefforwardRef

useRef

const refContainer = useRef(initialValue);

useRef返回一個可變的ref對象,其.current屬性被初始化為傳入的參數(initialValue)。返回的ref對象在整個生命周期內保持不變。
下面看一個例子

function TextInputWithFocusButton() {
  // 關鍵代碼
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // 關鍵代碼,`current` 指向已掛載到 DOM 上的文本輸入元素
    inputEl.current.focus();
  };
  return (
    <>
      // 關鍵代碼
      <input ref={inputEl} type="text" />

      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

效果如下
Feb-23-2020 21-10-30.gif

可以看到我們點擊button,先通過useRef創建一個ref對象inputEl,然后再將inputEl賦值給inputref,最后,通過inputEl.current.focus()就可以讓input聚焦。
然后,我們再想下,如果input不是個普通的dom元素,而是個組件,該怎么辦呢?
這就牽扯到另外一個api,forwardRef

forwardRef

我們修改一下上面的例子,將input單獨封裝成一個組件TextInput

const TextInput =  forwardRef((props,ref) => {
  return <input ref={ref}></input>
})

然后用TextInputWithFocusButton調用它

function TextInputWithFocusButton() {
  // 關鍵代碼
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // 關鍵代碼,`current` 指向已掛載到 DOM 上的文本輸入元素
    inputEl.current.focus();
  };
  return (
    <>
      // 關鍵代碼
      <TextInput ref={inputEl}></TextInput>
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

可以看到React.forwardRef 接受一個渲染函數,其接收 props 和 ref 參數並返回一個 React 節點。
這樣我們就將父組件中創建的ref轉發進子組件,並賦值給子組件的input元素,進而可以調用它的focus方法。
至此為止,通過useRef+forwardRef,我們就可以在函數式組件中使用ref了。當然,這篇文章還遠不止如此,下面還要介紹兩個重要的知識點useImperativeHandle回調Ref,結合上面兩個api,讓你的代碼更加完美。

useImperativeHandle

有時候,我們可能不想將整個子組件暴露給父組件,而只是暴露出父組件需要的值或者方法,這樣可以讓代碼更加明確。而useImperativeHandleApi就是幫助我們做這件事的。
語法:

useImperativeHandle(ref, createHandle, [deps])

useImperativeHandle 可以讓你在使用 ref 時自定義暴露給父組件的實例值。
一個例子🌰:

const TextInput =  forwardRef((props,ref) => {
  const inputRef = useRef();
  // 關鍵代碼
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} />
})
function TextInputWithFocusButton() {
  // 關鍵代碼
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // 關鍵代碼,`current` 指向已掛載到 DOM 上的文本輸入元素
    inputEl.current.focus();
  };
  return (
    <>
      // 關鍵代碼
      <TextInput ref={inputEl}></TextInput>
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

這樣,我們也可以使用current.focus()來事input聚焦。這里要注意的是,子組件TextInput中的useRef對象,只是用來獲取input元素的,大家不要和父組件的useRef混淆了。

回調Ref

什么是回調Ref呢?
上面的例子,都有一個問題,就是當 ref 對象內容發生變化時,useRef 並不會通知你。變更 .current 屬性不會引發組件重新渲染,看下面這個例子。
比如下面這個例子:

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const [value, setValue] = useState("");
  useEffect(() => {
    setValue(inputEl.current.value);
  }, [inputEl]);
  const onButtonClick = () => {
    // `current` 指向已掛載到 DOM 上的文本輸入元素
    console.log("input值", inputEl.current.value);
    setValue(inputEl.current.value);
  };
  return (
    <>
      <div>
        子組件: <TextInput ref={inputEl}></TextInput>
      </div>
      <div>
        父組件: <input type="text" value={value} onChange={() => {}} />
      </div>
      <button onClick={onButtonClick}>獲得值</button>
    </>
  );
}
const TextInput = forwardRef((props, ref) => {
  const [value, setValue] = useState("");
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    value: inputRef.current.value,
  }));
  const changeValue = e => {
    setValue(e.target.value);
  };
  return <input ref={inputRef} value={value} onChange={changeValue}></input>;
});

Feb-23-2020 23-26-09.gif

可以看到,父組件獲取不到子組件實時的值,必須點擊按鈕才能獲取到,即使我寫了useEffect,希望它在inputEl改變的時候,重新設置value的值。
那怎么辦呢?這就需要回調Ref,我們改一下代碼

function TextInputWithFocusButton() {
  const [value, setValue] = useState("");
  const inputEl = useCallback(node => {
    if (node !== null) {
      console.log("TCL: TextInputWithFocusButton -> node.value", node.value)
      setValue(node.value);
    }
  }, []);
  
  const onButtonClick = () => {
    // `current` 指向已掛載到 DOM 上的文本輸入元素
    console.log("input值", inputEl.current.value);
    setValue(inputEl.current.value);
  };
  return (
    <>
      <div>
        子組件: <TextInput ref={inputEl}></TextInput>
      </div>
      <div>
        父組件: <input type="text" value={value} onChange={() => {}} />
      </div>
    </>
  );
}
const TextInput =  forwardRef((props,ref) => {
  const [value, setValue] = useState('')
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    value: inputRef.current.value
  }));
  const changeValue = (e) =>{
    setValue(e.target.value);
  }
  return <input ref={inputRef} value={value} onChange={changeValue}></input>
})

Feb-23-2020 23-32-09.gif

可以看到,這里我們輸入時,父組件就可以實時地拿到值了。
這里比較關鍵的代碼就是使用useCallback代替了useRefcallback ref會將當前ref的值變化通知我們。

好,以上就是整理的關於函數式組件中使用Ref的方法。
當然,其中關於api介紹的比較簡陋,大家看完可能不知所雲。因為此篇文章的主要目的,僅是將散落在官網中關於ref的相關方法進行一下整合,形成一個使用ref的思路,關於api的更深入的了解,還請移步React官網
如有不足,歡迎指出。

參考文獻:
官網中useRef useImperativeHandle Api的介紹
官網中forwardRef的介紹
官網中回調Ref的介紹


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM