如何深入理解React的ref 屬性

概述

首先,Refs 和 ref 是兩個概念,Refs 是 React 提供的可用特定 API 創建的一個對象。該對象的結構如下:

這個對象隻有一個屬性就是 current ,那麼這個對象是用來幹嘛的呢?

Refs 允許我們訪問 DOM 節點或在 render 方法中創建的 React 元素。(DOM節點就是指原生DOM元素,在render()方法中創建的 React 元素就是指 React 的類組件元素)

我們可以想象這樣一個需求,兩個兄弟元素,一個是 div ,一個是 button。現在想實現點擊 button,改變 div 的背景顏色。在原生的 DOM 技術中,我們可以在 button 的點擊函數裡使用 document.querySelector(‘xxx’) 的方式選中 div 節點,然後改變其背景樣式。但是無論是在 Vue 還是 React 這樣的框架中,頁面元素都是動態生成的,無法使用 DOM API 獲取的方式。而且 React 中大部分操作的元素不是 原生DOM元素,而是 React 元素。 那麼如何選擇到某一個 原生DOM元素 或者 React 元素呢?

其實,理論上,我們不需要進行任何的選擇操作,這樣會失去前端框架中組件獨立的概念。一般情況下是通過 組件通信 的方式進行事件的處理的。上述的情況可以使用 EventBus 的方式進行組件通信,button 的點擊事件中進行自定義事件的觸發,在 div 中進行自定義事件的監聽,讓 button 以事件通知的方式告知 div 讓其改變背景色,而不是在 button 的事件中直接獲取 div 進行操作。

但是 React 為我們提供瞭直接訪問 DOM元素 和 React 元素的方式,就是通過 Refs。使用的方式很簡單,就是,為想要訪問的元素上添加 ref 屬性,將 Refs 對象附加到 ref 屬性上,那麼此時 Refs 對象的 current 屬性就不再是空,而是對應的 DOM元素 或 React 元素實例瞭。

1. Refs 對象的創建

在 React 中,創建 Ref 對象的方式有兩種:

1.1 React.createRef()

使用 React.createRef() 的方式可以創建一個 Ref 對象,可通過附加到 ref 屬性上訪問一個 原生DOM元素 或者 class 組件。
這種方式既可以在函數組件中使用,也可以在class組件中使用。

1.2 React.useRef(initialValue)

在 React 16.8 中新增瞭 Hook 後,又多瞭一個可以創建 Ref 對象的 Hook。即 React.useRef(initialValue)。
useRef 返回一個可變的 ref 對象,其 .current 屬性被初始化為傳入的參數(initialValue)。返回的 ref 對象在組件的整個生命周期內保持不變。
這種方式隻能在函數組件中使用。

2. ref 屬性的使用

ref 屬性隻能被添加到 原生DOM元素 或者 React的 class 組件上。不能在 函數組件 上使用 ref 屬性,因為函數組件沒有實例。

若想在函數組件上使用 ref 屬性,可以通過 React.forwardRef 將 Ref 轉發到函數組件內部的 原生 DOM 元素上。

2.1 為原生DOM元素添加 ref

class類組件內部

class App extends React.Component{
    constructor(props){
        super(props)
        this.myRef = React.createRef()
    } 
    componentDidMount(){
        console.log(this.myRef)
        console.log(this.myRef.current)
    }
    render(){
        return (
            <div ref={this.myRef}>我是App組件</div>
        )
    }
}

函數組件內部

const App = ()=>{
    const myRef = React.useRef(null)
    //const myRef = React.createRef() 兩種創建 ref 對象的方式都可以
    React.useEffect(()=>{
        console.log(myRef)
        console.log(myRef.current)
    },[]) //模擬生命周期
    return (
        <div ref={myRef}>我是函數組件內部使用ref的div</div>
    )
}

2.2 為class組件添加 ref

class ClassChild extends React.Component{
    render(){
        return (
            <div>我是App組件的 class 子組件 ClassChild</div>
        )
    }
}

class App extends React.Component{
    constructor(props){
        super(props)
        this.myRef = React.createRef()
    } 
    componentDidMount(){
        console.log(this.myRef)
        console.log(this.myRef.current)
    }
    render(){
        return (
            <ClassChild ref={this.myRef}/>
        )
    }
}

2.3 為class組件轉發的原生DOM元素添加 ref

ref 轉發原理就是將父組件中定義的 ref 對象當作普通屬性的方式傳遞給子組件,然後子組件通過 props 接收再賦值給自己 DOM元素 上。

class ClassChild extends React.Component{
    render(){
        return (
            <div ref={this.props.refProp}>我是App組件的 class 子組件 ClassChild</div> //添加瞭 ref
        )
    }
}

class App extends React.Component{
    constructor(props){
        super(props)
        this.myRef = React.createRef()
    } 
    componentDidMount(){
        console.log(this.myRef)
        console.log(this.myRef.current)
    }
    render(){
        return (
            <ClassChild refProp={this.myRef}/> //作為普通屬性傳遞
        )
    }
}

2.4 為函數組件轉發的原生DOM元素添加 ref

根據class類組件轉發的原理,我想到的實現方法如下:

const FunChild = (props)=>{
    return (
        <div ref={props.refProp}>我是函數組件 FunChild</div>
    )
}
class App extends React.Component{
    constructor(props){
        super(props)
        this.myRef = React.createRef()
    } 
    componentDidMount(){
        console.log(this.myRef)
        console.log(this.myRef.current)
    }
    render(){
        return (
            <FunChild refProp={this.myRef}/>
        )
    }
}

這種實現方式是可以的,但這不是在 函數組件 上直接使用 ref 屬性的方式,React 提供瞭在函數組件上直接使用 ref 的方式,就是使用 React.forwardRef 創建 React 元素。

React.forwardRef

const FunChild = React.forwardRef((props, ref)=>{
    return (
        <div ref={ref}>我是函數組件 FunChild</div>
    )
}) // 使用 React.forwardRef 改造函數組件
class App extends React.Component{
    constructor(props){
        super(props)
        this.myRef = React.createRef()
    } 
    componentDidMount(){
        console.log(this.myRef)
        console.log(this.myRef.current)
    }
    render(){
        return (
            <FunChild ref={this.myRef}/>  //直接給函數組件傳遞 ref
        )
    }
}

感覺 React.forwardRef 就是把 ref 屬性單獨從 props 中抽離出來瞭。
盡管上述方式實現瞭在函數組件上使用 ref 屬性,但此時的 Ref 對象是訪問的函數組件內部的 原生DOM元素 或其他 class組件。也就是說,在這裡函數組件隻是起到瞭一個轉發的作用。

3. 回調 Refs

上述的方式中,我們都是通過創建一個 Ref 對象,通過 ref 屬性的方式掛載到 原生DOM元素 或者 class 組件上用於訪問該元素或實例。
實際上,ref 屬性除瞭可以接收一個 Ref 對象外,還可以接收一個回調函數。
當 ref 屬性接收 Ref 對象時,會將其對應的 DOM元素 或者 class組件實例 直接賦值給 Ref 對象中的 current 屬性上。而當 ref 屬性接收一個回調函數時,會將其對應的 DOM元素 或 class組件實例作為回調函數的參數調用回調函數。
因此我們可以通過回調 Refs 的方式不依靠 Ref 對象,更靈活地控制要訪問的元素或實例。

class App extends React.Component{
    constructor(props){
        super(props)
        this.myRef = null
        this.setMyRef = (element)=>{
            this.myRef = element
        }
    } 
    componentDidMount(){
        console.log(this.myRef)
    }
    render(){
        return (
            <div ref={this.setMyRef}>我是App組件</div>
        )
    }
}

以上就是如何深入理解React的ref 屬性的詳細內容,更多關於深入理解React的ref 屬性的資料請關註WalkonNet其它相關文章!

推薦閱讀: