useRef
是 React 中常用的 Hook 之一,它返回一個可變的 ref 對象,其?.current
?屬性被初始化為傳入的參數(initialValue
)。返回的 ref 對象在組件的整個生命周期內保持不變。
以下是一些使用?useRef
?的場景和示例:
1、存儲React?DOM 元素
我們首先回憶一下 vue 中的 ref:
????????ref 被用來給元素或子組件注冊引用信息 —— vue 官網。引用信息將會注冊在父組件的?$refs
?對象上。
請看示例:
<!-- `vm.$refs.p` will be the DOM node -->
<p ref="p">hello</p><!-- `vm.$refs.child` will be the child component instance -->
<child-component ref="child"></child-component>
????????如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子組件上,引用就指向組件實例。
那么 react 中的 ref 是否也是這個作用?我們可以從其用法上去做判斷。
React 支持一個特殊的、可以附加到任何組件上的 ref 屬性。此屬性可以是一個由?React.createRef()
?函數創建的對象、或者一個回調函數、或者一個字符串(遺留 API) —— 官網-ref
????????于是我們知道在 react 中 ref 屬性可以是一個對象、回調函數,亦或一個字符串。
1.1、useRef使用
??存儲React元素:你可以使用?useRef
?來存儲一個React元素,然后在需要的時候使用這個元素。這在一些情況下很有用,比如你需要引用一個在組件的子元素列表中的特定元素。
簡單示例:
import { useRef, useEffect } from 'react';function MyComponent() {const inputRef = useRef(null);useEffect(() => {inputRef.current.focus(); //組件掛載時使其獲得焦點}, []);return (<div><input type="text" ref={inputRef} /></div>);
}
上面的代碼使用 useRef
獲取 input 元素的引用,并在組件掛載時使其獲得焦點。
1.2、對比類組件中的refs
1.2.1、String 類型的 Refs
下面這個例子將 ref 分別應用在?dom 元素
和子組件
中:
class ASpan extends React.Component {render() {return <span>click</span>}
}class EventDemo1 extends React.Component {handleClick() {console.log(this.refs)console.log(this.refs.aButton.innerHTML)}render() {return (// 箭頭函數<button ref="aButton" onClick={() => this.handleClick()}><ASpan ref="aSpan" /></button>);}
}
點擊按鈕,控制臺輸出:
{aSpan: ASpan, aButton: button}
<span>click</span>
Tip:用法上和 vue 中的?vm.$refs
?非常相似。
注:如果你目前還在使用?this.refs.textInput
?這種方式訪問 refs ,我們建議用回調函數
或?createRef API
?的方式代替 —— 官網-過時 API:String 類型的 Refs
1.2.2、回調 Refs
React 也支持另一種設置 refs 的方式,稱為“回調 refs”。它能助你更精細地控制何時 refs 被設置和解除 —— 官網-回調 Refs
將字符串式 Refs 示例改成回調式
。請看示例:
class EventDemo1 extends React.Component {handleClick() {console.log(this.refs)console.log(this.button.innerHTML)}setButtonRef = (element) => {this.button = element}render() {return (// 使用 `ref` 的回調函數將按鈕 DOM 節點的引用存儲到 React// 實例上(比如 this.button)<button ref={this.setButtonRef} onClick={() => this.handleClick()}>click</button>);}
}
點擊按鈕,控制臺輸出:
{}
click
????????回調函數中接受 React 組件實例或 HTML DOM 元素作為參數,以使它們能在其他地方被存儲和訪問。
1.2.3、內聯函數
可以將 refs 回調函數直接寫在 ref 中。就像這樣:
// 與上面示例效果相同
<button ref={element => this.button = element} onClick={() => this.handleClick()}>click
</button>
1.2.4、關于回調次數
如果 ref 回調函數是以內聯函數的方式定義的,在更新過程中它會被執行兩次,第一次傳入參數 null,然后第二次會傳入參數 DOM 元素 —— 官網-關于回調 refs 的說明
請看示例:
class EventDemo1 extends React.Component {state = { date: new Date() }constructor() {super()setInterval(() => {this.setState({ date: new Date() })}, 3000)}render() {return (<button ref={element => { this.button = element; console.log('ref'); }}>click {this.state.date.toLocaleTimeString()}</button>);}
}
首先輸出?ref
,然后每過 3 秒就會輸出 2 次?ref
。
Tip:大多數情況下它是無關緊要的 —— 官網
1.2.5、createRef API
將回調 refs 的例子改成 createRef 形式。請看示例:
class EventDemo1 extends React.Component {constructor() {super()this.button = React.createRef()// this.textInput = React.createRef()}handleClick() {// dom 元素或子組件可以在 ref 的 current 屬性中被訪問console.log(this.button.current.innerHTML)}render() {return (<button ref={this.button} onClick={() => this.handleClick()}>click</button>)}
}
每點擊一下 button,控制臺將輸出一次?click
。
Refs 是使用 React.createRef() 創建的,并通過 ref 屬性附加到 React 元素。在構造組件時,通常將 Refs 分配給實例屬性,以便可以在整個組件中引用它們 —— 官網-創建 Refs
如果需要在增加一個 ref,則需要再次調用?React.createRef()
。
1.2.6、在函數組件中使用 ref
你不能在函數組件上使用 ref 屬性,因為他們沒有實例 —— 官網-訪問 Refs
而通過?useRef
?Hook 能讓我們在函數組件使用?ref
。重寫 class 組件 EventDemo1:
function EventDemo1() {const button = React.useRef(null)function handleClick() {console.log(button.current.innerHTML)}return (<button ref={button} onClick={() => handleClick()}>click</button>)
}
每點擊一下 button,控制臺將輸出一次?click
。
const refContainer = useRef(initialValue);
useRef 返回一個可變的 ref 對象,其 .current 屬性被初始化為傳入的參數(initialValue) —— 官網-useref???????
2、緩存值(存在整個生命周期中)
????????存儲一個變量或者常量:你可以使用?useRef
?來存儲一個變量或常量,以便在組件的整個生命周期中使用。
??useEffect
里面的state的值,是固定的,這個是有辦法解決的,就是用useRef
,可以理解成useRef
的一個作用:就是相當于全局作用域,一處被修改,其他地方全更新。
????????本質上,useRef
?就像是可以在其?.current
?屬性中保存一個可變值的“盒子”。你應該熟悉 ref 這一種訪問 DOM 的主要方式。如果你將 ref 對象以?<div ref={myRef} />
?形式傳入組件,則無論該節點如何改變,React 都會將 ref 對象的?.current
?屬性設置為相應的 DOM 節點。然而,useRef()
?比?ref
?屬性更有用。它可以很方便地保存任何可變值,其類似于在 class 中使用實例字段的方式。
????????請記住,當 ref 對象內容發生變化時,useRef
?并不會通知你。變更?.current
?屬性不會引發組件重新渲染。如果想要在 React 綁定或解綁 DOM 節點的 ref 時運行某些代碼,則需要使用回調 ref 來實現。
import { useRef, useEffect } from 'react';function MyComponent() {const prevCountRef = useRef(0);const [count, setCount] = useState(0);useEffect(() => {prevCountRef.current = count;}, [count]);return (<div><p>prev count: {prevCountRef.current}</p><p>current count: {count}</p><button onClick={() => setCount(count + 1)}>Increment</button></div>);
}
????????在這個例子中,我們使用?useState
?來存儲一個計數器的值,同時也使用?useRef
?來創建一個引用緩存了 count
的前一個值,以便在組件的整個生命周期中訪問這個值。當我們點擊按鈕count
更新時,同時?prevCountRef.current
?的值也會更新。注意,雖然?prevCountRef.current
?的值會隨著?count
?的變化而變化,但?prevCountRef
本身是一個可變的引用,我們可以在任何地方通過prevCountRef.current
?來獲取當前的值,用于記錄前一次渲染的狀態。
3、在 useEffect 中獲取最新的值
import { useRef, useEffect } from 'react';function MyComponent(props) {const prevPropsRef = useRef(null);useEffect(() => {prevPropsRef.current = props;});// ...return (// ...);
}
????????上面的代碼使用 useRef
緩存了 props
的前一個值,可以用于比較前后兩次 props
的變化情況。
使用 useRef
的原因是,在函數組件中使用普通的變量無法保證變量值的實時性,因為每次重新渲染組件時,普通變量都會重新聲明,而 useRef
返回的是一個固定的引用,不會因為重新渲染而改變。