1. 程式人生 > >React基礎知識之Ref回撥函式處理

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庫

參考資料: