Refs 提供了一種方式,用於訪問在 render 方法中創建的 DOM 節點或 React 元素。
在標准的React數據流中,props是使得父組件和子組件之間交互的唯一方式。你通過props重新渲染子組件。然而,有些情況需要你必須在數據流之外修改一個子組件。這個子組件可以是一個React組件的實例,或者是一個DOM元素。對於這兩種情況,React提供了解決方式。
什么時候使用refs
以下有幾種合適的情況使用refs:
- 處理焦點,文本選擇或者媒體播放
- 觸發強制的動畫
- 整合第三方DOM庫
避免使用refs在可以聲明式地解決的東西上。
舉個例子,不使用open()和close()方法在Dialog組件上,而是傳遞一個isOpen屬性給它。
不要過度使用 Refs
你可能首先會想到在你的應用程序中使用 refs 來更新組件。如果是這種情況,請花一點時間,重新思考一下 state 屬性在組件層中位置。通常你會想明白,提升 state 所在的組件層級會是更合適的做法。有關示例,請參考狀態提升.
創建 Refs
使用 React.createRef() 創建 refs,通過 ref 屬性來獲得 React 元素。當構造組件時,refs 通常被賦值給實例的一個屬性,這樣你可以在組件中任意一處使用它們.
class MyComponent extends React.Component { constructor(props) { super(props); this.myRef = React.createRef(); } render() { return <div ref={this.myRef} />; } }
訪問 Refs
當一個 ref 屬性被傳遞給一個 render 函數中的元素時,可以使用 ref 中的 current 屬性對節點的引用進行訪問。
const node = this.myRef.current;
ref的值取決於節點的類型:
當 ref 屬性被用於一個普通的 HTML 元素時,React.createRef() 將接收底層 DOM 元素作為它的 current 屬性以創建 ref 。
當 ref 屬性被用於一個自定義類組件時,ref 對象將接收該組件已掛載的實例作為它的 current 。
你不能在函數式組件上使用 ref 屬性,因為它們沒有實例。
下面的例子說明了這些差異。
為 DOM 元素添加 Ref
以下代碼使用 ref 存儲對 DOM 節點的引用:
class CustomTextInput extends React.Component { constructor(props) { super(props); // 創建 ref 存儲 textInput DOM 元素 this.textInput = React.createRef(); this.focusTextInput = this.focusTextInput.bind(this); } focusTextInput() { // 直接使用原生 API 使 text 輸入框獲得焦點 // 注意:通過 "current" 取得 DOM 節點 this.textInput.current.focus(); } render() { // 告訴 React 我們想把 <input> ref 關聯到構造器里創建的 `textInput` 上 return ( <div> <input type="text" ref={this.textInput}} /> <input type="button" value="Focus the text input" onClick={this.focusTextInput} /> </div> ); } }
React 會在組件加載時將 DOM 元素傳入 current 屬性,在卸載時則會改回 null。ref 的更新會發生在componentDidMount 或 componentDidUpdate 生命周期鈎子之前。
為類組件添加 Ref
如果我們想要包裝上面的 CustomTextInput ,來模擬掛載之后立即被點擊的話,我們可以使用 ref 來訪問自定義輸入,並手動調用它的 focusTexInput 方法:
class AutoFocusTextInput extends React.Component { constructor(props) { super(props); this.textInput = React.createRef(); } componentDidMount() { this.textInput.current.focusTextInput(); } render() { return ( <CustomTextInput ref={this.textInput} /> ); } }
需要注意的是,這種方法僅對 class 聲明的 CustomTextInput 有效:
class CustomTextInput extends React.Component { // ... }
Refs 與函數式組件
你不能在函數式組件上使用 ref 屬性,因為它們沒有實例:
function MyFunctionalComponent() { return <input />; } class Parent extends React.Component { constructor(props) { super(props); this.textInput = React.createRef(); } render() { // 這將 *不會* 工作! return ( <MyFunctionalComponent ref={this.textInput} /> ); } }
如果你想使用 ref,就像你想使用生命周期方法或者 state 一樣,應該將其轉換為 class 組件。
但是,你可以在函數式組件內部使用 ref,只要它指向一個 DOM 元素或者 class 組件:
function CustomTextInput(props) { // 這里必須聲明 textInput,這樣 ref 回調才可以引用它 let textInput = null; function handleClick() { textInput.focus(); } return ( <div> <input type="text" ref={(input) => { textInput = input; }} /> <input type="button" value="Focus the text input" onClick={handleClick} /> </div> ); }
對父組件暴露 DOM 節點
在極少數情況下,你可能希望從父組件訪問子節點的 DOM 節點。通常不建議這樣做,因為它會破壞組件的封裝,但偶爾也可用於觸發焦點或測量子 DOM 節點的大小或位置。
雖然你可以向子組件添加 ref,但這不是一個理想的解決方案,因為你只能獲取組件實例而不是 DOM 節點。並且,它還在函數式組件上無效。
如果你使用 React 16.3 或更高, 這種情況下我們推薦使用 ref 轉發。 Ref 轉發使組件可以像暴露自己的 ref 一樣暴露子組件的 ref。關於怎樣對父組件暴露子組件的 DOM 節點,在 ref 轉發文檔 中有一個詳細的例子。
如果你使用 React 16.2 或更低,或者你需要比 ref 轉發更高的靈活性,你可以使用 這個替代方案 將 ref 作為特殊名字的 prop 直接傳遞。
可能的話,我們不建議暴露 DOM 節點,但有時候它會成為救命稻草。注意這些方案需要你在子組件中增加一些代碼。如果你對子組件的實現沒有控制權的話,你剩下的選擇是使用 findDOMNode(),但是不推薦。
回調 Refs
React 也支持另一種設置 ref 的方式,稱為“回調 ref”,更加細致地控制何時 ref 被設置和解除。
不同於傳遞 createRef() 創建的 ref 屬性,你會傳遞一個函數。這個函數接受 React 組件的實例或 HTML DOM 元素作為參數,以存儲它們並使它們能被其他地方訪問。
下面的例子描述了一種通用的范例:使用 ref 回調函數,在實例的屬性中存儲對 DOM 節點的引用。
class CustomTextInput extends React.Component { constructor(props) { super(props); this.textInput = null; this.setTextInputRef = element => { this.textInput = element; }; this.focusTextInput = () => { // 直接使用原生 API 使 text 輸入框獲得焦點 if (this.textInput) this.textInput.focus(); }; } componentDidMount() { // 渲染后文本框自動獲得焦點 this.focusTextInput(); } render() { // 使用 `ref` 的回調將 text 輸入框的 DOM 節點存儲到 React // 實例上(比如 this.textInput) return ( <div> <input type="text" ref={this.setTextInputRef} /> <input type="button" value="Focus the text input" onClick={this.focusTextInput} /> </div> ); } }
React 將在組件掛載時將 DOM 元素傳入ref 回調函數並調用,當卸載時傳入 null 並調用它。ref 回調函數會在 componentDidMout 和 componentDidUpdate 生命周期函數前被調用
你可以在組件間傳遞回調形式的 refs,就像你可以傳遞通過 React.createRef() 創建的對象 refs 一樣。
function CustomTextInput(props) { return ( <div> <input ref={props.inputRef} /> </div> ); } class Parent extends React.Component { render() { return ( <CustomTextInput inputRef={el => this.inputElement = el} /> ); } }
在上面的例子中,Parent 傳遞給它的 ref 回調函數作為 inputRef 傳遞給 CustomTextInput,然后 CustomTextInput 通過 ref屬性將其傳遞給 <input>。最終,Parent 中的 this.inputElement 將被設置為與 CustomTextIput 中的 <input> 元素相對應的 DOM 節點
注意
如果 ref 回調以內聯函數的方式定義,在更新期間它會被調用兩次,第一次參數是 null ,之后參數是 DOM 元素。這是因為在每次渲染中都會創建一個新的函數實例。因此,React 需要清理舊的 ref 並且設置新的。通過將 ref 的回調函數定義成類的綁定函數的方式可以避免上述問題,但是大多數情況下無關緊要