React基礎知識之Ref回撥函式處理
程式碼地址請在github檢視,如果有新內容,我會定時更新,也歡迎您star,issue,共同進步
1.為DOM元素新增Ref
react支援一個ref屬性,該屬性可以新增到任何的元件上。該ref屬性接收一個回撥函式,這個回撥函式在元件掛載或者解除安裝的時候被呼叫。當ref用於一個HTML元素的時候,ref指定的回撥函式在呼叫的時候會接收一個引數,該引數就是指定的DOM元素。如下面的例子使用ref回撥函式來儲存對DOM節點的引用:
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
this .focus = this.focus.bind(this);
}
focus() {
// Explicitly focus the text input using the raw DOM API
this.textInput.focus();
}
render() {
// Use the `ref` callback to store a reference to the text input DOM
// element in an instance field (for example, this.textInput).
return (
<div >
<input
type="text"
ref={(input) => { this.textInput = input; }} />
//此時input引數就是表示該DOM本身
<input
type="button"
value="Focus the text input"
onClick={this.focus}
/>
</div>
);
}
}
當元件掛載的時候React會給ref回撥函式傳入當前的DOM,在元件解除安裝的時候會傳入null。使用ref回撥函式給我們的class新增屬性是訪問元件DOM的常用方法,就像上面的例項一樣。當然你也可以使用下面更加簡短的寫法:
ref={input=>this.textInput=input}
2.為元件Component新增Ref
當ref屬性用於一個class指定的自定義元件的時候,ref回撥函式會接收到一個掛載的元件例項作為引數。比如,下面的例子展示了當CustomTextInput被掛載後馬上模擬被點選:
class AutoFocusTextInput extends React.Component {
componentDidMount() {
//呼叫CustomTextInput例項的focus方法
this.textInput.focus();
}
render() {
return (
<CustomTextInput
ref={(input) => { this.textInput = input; }} />
//該ref回撥函式會接收到一個掛載的元件例項作為引數
);
}
}
當然,下面的CustomTextInput必須使用class來宣告:
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
this.focus = this.focus.bind(this);
}
focus() {
// Explicitly focus the text input using the raw DOM API
this.textInput.focus();
}
render() {
// Use the `ref` callback to store a reference to the text input DOM
// element in an instance field (for example, this.textInput).
return (
<div>
<input
type="text"
ref={(input) => { this.textInput = input; }} />
<input
type="button"
value="Focus the text input"
onClick={this.focus}
/>
</div>
);
}
}
注意,上面的ref回撥函式接收到的是一個CustomTextInput的例項,在componentDidMount中呼叫的是CustomTextInput例項的focus方法,但是該focus方法必須在CustomTextInput這個class中存在才行。下面的宣告將會報錯,因為CustomTextInput本身就沒有focus方法:
class CustomTextInput extends React.Component {
render(){
return (
<input placeholder="自定義元件例項"/>
)
}
}
報錯資訊如下:
Uncaught TypeError: this.textInput.focus is not a function at AutoFocusTextInput.componentDidMount (:24:22)
3.Ref與函式式宣告元件
你不能在函式式宣告元件中使用ref,因為他們不存在例項,如下面的例子就是錯誤的:
function MyFunctionalComponent() {
return <input />;
}
class Parent extends React.Component {
render() {
// This will *not* work!
return (
<MyFunctionalComponent
ref={(input) => { this.textInput = input; }} />
);
}
}
如果你想要使用ref那麼你必須轉化為class宣告。當然,在函式元件內部你依然可以使用ref屬性指向DOM元素或者class元件:
function CustomTextInput(props) {
// textInput must be declared here so the ref callback can refer to it
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>
);
}
4.為父元件暴露一個DOM的ref屬性
在一些情況下,你可能想要從父級元件中訪問子級元件的DOM節點。當然這是不推薦的,因為它破壞了元件的結構。但是,它在觸發子級元件DOM的焦點,獲取子級元件大小和子級元件位置的時候非常有用。
你可以為子級元件新增一個ref屬性,當然這不是理想選擇,因為此時你獲取到的是一個子級元件例項而不是一個DOM節點。而且,這對於函式式元件是沒有作用的(見上面的例子)。
在這種情況下,我們推薦在子級元件中暴露一個特殊的屬性。這個子級元件會接收一個函式作為prop屬性,而且該函式的名稱是任意的(例如inputRef),同時將這個函式賦予到DOM節點作為ref屬性。這樣,父級元件會將它的ref回撥傳遞給子級元件的DOM。這種方式對於class宣告的元件和函式式宣告的元件都是適用的:
function CustomTextInput(props) {
return (
<div>
//子級元件接收到父級元件傳遞過來的inputRef函式
//當子級元件例項化的時候會將該input傳入到該函式中,此時父級元件會
//接收到子級元件的DOM結構(input元素)
<input ref={props.inputRef} />
</div>
);
}
class Parent extends React.Component {
render() {
return (
<CustomTextInput
//為子級元件傳入一個inputRef函式
inputRef={el => this.inputElement = el}
\/>
);
}
}
在上面的例子中,Parent將他的ref回撥函式通過inputRef這個屬性傳遞給CustomTextInput,而CustomTextInput將這個函式作為input元素的ref屬性。最後,Parent元件中的this.inputElement將得到子級元件的input對應的DOM元素。注意:inputRef並沒有特殊的含義,但是在子級元件中必須作為ref屬性的值。
這種形式的又一個優點在於:可以跨越多個元件層級。設想一種情況:Parent元件不需要DOM節點的引用,但是父級元件的父級元件(Grandparent)需要,這樣的話我們可以給Grandparent指定一個inputRef屬性,從而傳遞給Parent,最後通過Parent元件傳遞給CustomTextInput:
return (
<div>
<input ref={props.inputRef} />
</div>
);
}
function Parent(props) {
return (
<div>
My input: <CustomTextInput inputRef={props.inputRef} />
</div>
);
}
class Grandparent extends React.Component {
render() {
return (
<Parent
inputRef={el => this.inputElement = el}
\/>
);
}
}
這樣的話,我們的GrandParent中的this.inputElement也會被設定為CustomTextInput中的input對應的DOM節點。當然,這種方式必須要你對子級元件具有完全控制,如果沒有,那麼你可以使用findDOMNode(),但是我們並不鼓勵這麼做。
5.ref遺留的問題
以前的ref屬性獲取到的是字串,而DOM節點通過this.refs.textInput來獲取。這是因為string型別的ref有一定的問題,在以後的react版本中將會被移除。如果你現在依然在使用this.refs.textInput來訪問refs,那麼建議您使用回撥函式來替代!
6.ref存在的問題以及ref常用情況
6.1 Ref存在的問題
如果ref回撥函式以inline函式的方式來指定,那麼在元件更新的時候ref回撥會被呼叫2次。第一次回撥的時候傳入的引數是null,而第二次的時候才真正的傳入DOM節點。這是因為,每次渲染的時候都會產生一個新的函式例項(每次都會產生一個新的函式,而不是單例模式或者設定到原型鏈中的函式),而React需要清除前一個ref,然後才設定一個新的ref。通過在class中指定ref回撥函式可以有效的避免這種情況。但是,在大多數情況下這並不會有什麼影響。
6.2 Ref常用情況
第一:管理焦點,文字選擇,媒體播放(媒體回放)
第二:觸發動畫
第三:整合第三方的DOM庫
參考資料: