React函數式組件使用Ref

目錄:html

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

簡介

你們都知道React中的ref屬性能夠幫助咱們獲取子組件的實例或者Dom對象,進而對子組件進行修改,是一個很方便的特性。在傳統類組件中,咱們經過使用 React.createRef() 建立的,並經過 ref屬性附加到 React 元素來使用。而隨着hooks的愈來愈普遍的使用,咱們有必要了解一下在函數式組件中,如何使用Ref.
想要在函數式組件中使用Ref,咱們必須先了解兩個Api,useRefforwardRefnode

useRef

const refContainer = useRef(initialValue);

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

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.gifapi

能夠看到咱們點擊button,先經過useRef建立一個ref對象inputEl,而後再將inputEl賦值給inputref,最後,經過inputEl.current.focus()就可讓input聚焦。
而後,咱們再想下,若是input不是個普通的dom元素,而是個組件,該怎麼辦呢?
這就牽扯到另一個api,forwardRefdom

forwardRef

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

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

而後用TextInputWithFocusButton調用它code

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,讓你的代碼更加完美。htm

useImperativeHandle

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

useImperativeHandle(ref, createHandle, [deps])

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

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的介紹

相關文章
相關標籤/搜索