React中的Ref


  React中ref是一個對象,它有一個current屬性,可以對這個屬性進行操作,用於獲取DOM元素和保存變化的值。什么是保存變化的值?就是在組件中,你想保存與組件渲染無關的值,就是JSX中用不到的或不顯示到頁面上的值,比如setTimeout的返回的ID,就可以把這個值放到ref中。為什么要放到ref中,因為更改ref的值,不會引起組件的重新渲染,因為值與渲染無關,它也不應該引起組件渲染。怎么獲取ref對象呢?調用useRef()函數和createRef()函數,它們返回ref對象。在組件的整個生命周期中,ref對象一直存在。組件創建,更准確地說法是,組件掛載,ref對象創建,組件銷毀,ref對象銷毀。

  useRef是一個React Hooks,在函數組件中使用,它還可以接受一個參數,用於初始化useRef返回的對象的current屬性。

const ref = useRef(initialValue);

  使用useRef獲取DOM元素,就是把ref對象賦給react element的ref屬性, 每一個react element都有一個ref屬性。組件掛載后,ref對象的current屬性,就自動指向DOM元素

import React, { useRef } from "react";

const CustomTextInput = () => {
  const textInput = useRef();

  const focusTextInput = () => textInput.current.focus();

  return (
    <>
      <input type="text" ref={textInput} />
      <button onClick={focusTextInput}>Focus the text input</button>
    </>
  );
}

  組件掛載完成,textInput.current指向input輸入框,就可以直接調用輸入框的focus方法。   

  使用useRef保存變量的值,直接把變量或值,賦值給ref對象的current屬性就可以了。

import React, { useRef, useEffect } from "react";

const Timer = () => {
  const intervalRef = useRef();

  useEffect(() => {
    const id = setInterval(() => {
      console.log("A second has passed");
    }, 1000);
    
    intervalRef.current = id;
    
    return () => clearInterval(intervalRef.current);
  });

  const handleCancel = () => clearInterval(intervalRef.current);
  
  return (
    <>
      //...
    </>
  );
}

  這里要注意的是更新ref對象的值,是一個side effect,因為這個值不參與渲染,更新值是React渲染之外,要做的事情,所以要放到useEffect或useLayoutEffect中,放到事件處理函數中也可以。如果以下有代碼

import React, { useRef } from "react";

const RenderCounter = () => {
  const counter = useRef(0);
  counter.current = counter.current + 1;
  
  return (
    <h1>{`The component has been re-rendered ${counter} times`}</h1>
  );
};

   最好改成   

import React, { useRef } from "react";

const RenderCounter = () => {
  const counter = useRef(0);
  
  useEffect(() => {
    counter.current = counter.current + 1;
  }); 
  
  return (
    <h1>{`The component has been re-rendered ${counter} times`}</h1>
  );
};

  函數組件中也可以使用createRef, 但當使用createRef時,每一次組件渲染時都會創建全新的ref對象,而不是每一次渲染都共用一個ref對象,性能會有問題,再說useRef就是代替createRef的,所以在函數組件中就沒有必要使用createRef了。

  其實,使用useRef,也可以獲取到子組件,直接調用子組件中的方法,不過就是用點麻煩,因為ref只能獲取到類組件的實例,也只有類才有實例。函數組件是沒有實例的,怎么獲取到它?使用forwardref, 把一個函數組件包起來,函數組件就多了一個ref屬性。子組件中用useImperativeHandle暴露方法。結合forwardRef 和useImperativeHandle。使用create-react-app 創建React項目,在src中創建一個Counter組件

import React from 'react';
import { useState } from "react"

const Counter = () => {
    const [count, setCount] = useState(0);

    const clickHandler = () => {
        setCount(c => c + 1);
    }
    
    return (
        <p>count is {count} </p>
    )
}

export default Counter;

  然后在App.js中引入

import React from 'react';
import Counter from "./Counter";

function App() {
  return (
    <React.Fragment>
      <Counter></Counter>
      <button>Add</button>
    </React.Fragment>
  );
}

export default App;

  此時,如果想點擊父組件App中的button來增加子組件的count,怎么辦?首先,子組件Counter,要把clickHandler方法暴露出來。做法,1,export的不是組件了,而是forwardRef(組件); 2,組件要接受參數ref,const Counter = (props, ref); 3,  在組件內部,使用useImperativeHandle,它的第一個參數是ref,第二個參數是回調函數,返回一個對象,對象中的屬性和方法,就可以在父組件中使用ref獲取到。

import React, { forwardRef, useImperativeHandle } from 'react';
import { useState } from "react"

// 組件被forwardRef之后,組件多了一個ref屬性
const Counter = (props, ref) => {
  const [count, setCount] = useState(0);

  const clickHandler = () => {
    setCount(c => c + 1);
  }
  // 第一個參數就是ref,暴露出click方法,供父組件使用
  useImperativeHandle(ref, () => {
    return ({
      click: clickHandler
    })
  })

  return (
    <p>count is {count} </p>
  )
}

// export forwardRef(組件)
export default forwardRef(Counter);

  其次,在父組件App中使用ref,引用子組件,並在button的click回調函數中使用ref

function App() {
  const counteRef = useRef();

  const handleClick = () => {
    counteRef.current.click();
  }

  return (
    <React.Fragment>
      <Counter ref={counteRef}></Counter>
      <button onClick={handleClick}>Add</button>
    </React.Fragment>
  );
}

  如果項目中使用了Redux和React-Redux,子組件export的是connect()(組件),是一個高階組件,那父組件中怎么引用子組件呢?如果是react-redux 6.0以前的版本,connect函數第四個參數設置為{ withRef: true },父組件getWrappedInstance()就可以獲取到包裹的子組件

connect(null, null, null, { withRef: true })(組件);

  如果是react-redux 6.0 以后的版本,使用 forwardRef: true , 代替{ withRef: true },父組件中的ref可以直接獲取到包包裹的子組件

connect(null, null, null, { forwardRef: true })(組件);

   


免責聲明!

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



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