React ref的基本使用

React出現後,提供了state, props,前端開發者無須在直接操做dom。React官方不推薦咱們直接訪問、操做DOM,可是,仍是爲咱們留了一個後門ref,方便訪問操做DOM。由於在某些特定的場景,必須使用ref來訪問DOM元素。好比:input的focus,媒體播放器、組件的位置,動畫,引入第三dom方庫。html

建立和獲取ref

在reactd的版本歷史上,出現了三種建立ref的方式:string ref,callback ref,React.createRef。不管哪一種方式,都是爲ref屬性賦值,ref和key同樣,都是關鍵字,爲React內部使用。 另外,值得注意的是,全部的ref獲取,最好在組件加載結束以後,不然沒法獲取值。由於組件加載後,dom才準備好了是吧。前端

string ref

class Test extends React.Component {
  componentDidMount(){
  	// 獲取
      console.log(this.refs.first);
    // <input value="first">
  }
  render() {
  	// 建立
      return <input value="first" ref="first" />
  }
}
複製代碼

string ref建立的ref的方法在React16.3以後的版本棄用了,而且官方表示,在16.3以前,儘可能使用callback ref來建立ref。由於string ref建立的ref帶有些問題,具體緣由見鏈接react

callback ref

class Test extends React.Component {
  componentDidMount(){
  // 獲取
    console.log(this.second);
  // <input value="second">
  }
  render() {
  // 建立
    return <input value="second" ref={(input) => {this.second = input }} />
  }
}
複製代碼

經過回調函數的方式建立ref,形勢上看上去稍微有些繁瑣。而且,callback以上面這種內聯的方式賦值,在組件發生了更新時,ref都會從新建立。可經過將這個回調函數變成類的方法來避免。git

class Test extends React.Component {
  componentDidMount(){
  // 獲取
    console.log(this.second);
  // <input value="second">
  }
  createRef = (dom) => {
  	this.second = dom;
  }
  render() {
  // 建立
    return <input value="second" ref={this.createRef} />
  }
}
複製代碼

React.createRef

class Test extends React.Component {
  constructor(props) {
      super(props);
      // 建立
      this.third = React.createRef();
  }
  componentDidMount(){
  // 獲取
      console.log(this.third.current);
    // <input value="third">
  }
  render() {
  // 賦值
      return <input value="third" ref={this.third} />
  }
}
複製代碼

React.ref是React16.3後新加的一個建立ref的方法,寫法相對於callback ref的寫法相對簡潔。將建立的ref賦值給不一樣的子元素,ref的current的值有所區別。github

不一樣子元素的ref值

這裏討論經過callback ref和React.createRef建立的ref,賦值給不一樣的子元素後,ref的取值的不一樣。app

HTML元素

爲HTML元素的ref賦值,獲取到的ref的值爲這個HTML元素對應的DOM元素。dom

class Test extends React.Component {
  second = React.creteRef()

  handleSubmit = () => {
      console.log(this.first);
      // <input value="first">
      console.log(this.second.current);
      // <input value="second">
  }

  render() {
  // 建立
    return (
      <div>
          <input value="first" ref={(input) => {this.first = input} } />
          <input value="second" ref={this.second} />
          <button onClick={this.handleSubmit} >提交</button>
      </div>
    )
  }
}
複製代碼

class類建立的React組件

爲class類建立的React組件的ref賦值後,最終獲取到的值爲這個React組件的實例。函數

class Test extends React.Component {

   second = React.createRef()

   handleSubmit = () => {
   	console.log(this.first);
     // Hello {props: {…}, context: {…}, refs: {…}, updater: {…}, _reactInternalFiber: FiberNode, …}
       console.log(this.second.current);
       // Hello {props: {…}, context: {…}, refs: {…}, updater: {…}, _reactInternalFiber: FiberNode, …}
   }

   render() {
       return (
           <div>
           	<Hello ref={(input) => {this.first = input}} />
               <Hello ref={this.second} />
               <button onClick={this.handleSubmit}>submit</button>
           </div>
       )
   }
}
複製代碼

function建立的React組件

遺憾的是,兩種方法均沒法爲function建立的React組件ref賦值,就算賦值,獲取到的最終結果爲null。工具

進階的ref使用

經過react提供的ref,父組件能夠獲取到具體的某個子元素,這是基本的用法。如下還將提到一些進階用法。動畫

React.forwardRef

這裏有個問題存在,有沒有辦法穿過父組件,獲取子元素?正好,與React.createRef一塊兒出世的還有一個用於解決這個問題的直接辦法React.forwarRef。其實,這個這個問題主要出如今HOC高階組件上,開發者能夠經過React.forwardRef獲取WrapperedComponnet,而不是外面的包裹層。

class FancyInput extends React.Component{
    render() {
        return <input value="fancyInput" />
    }
}

const HOCFn = (WrapperedComponent) => {
    class Test extends React.Component {

        render() {
            const { forwardRef, ...rest } = this.props;

            return (
                <WrapperedComponent ref={forwardRef} {...rest} />
            )
        }
    }

    return React.forwardRef((props, ref) => {
        return <Test forwardRef={ref} {...props} />
    });
}

const HocEdComp = HOCFn(FancyInput);

export default class NewComp extends React.Component {
    handleSubmit = () => {
        console.log(this.testRef);
        // FancyInput
    }
    render() {
        return (
            <div>
                <HocEdComp ref={(dom) => { this.testRef = dom }} />
                <button onClick={this.handleSubmit}>提交</button>
            </div>
        )
    }
};
複製代碼

最終,從上面的例子可知,this.testRef指向的是FancyInput,而非HocEdComp。

獲取資源子元素中指定的DOM

父元素能夠經過ref機制獲取html元素,React組件實例,固然這二者都是做爲父元素的子元素存在。在這裏咱們看到幾點侷限性:

  • 沒法獲取React組件中的某個dom元素
  • 沒法獲取function建立的組件

在這裏,能夠使用一些小技巧。看例子:

const First = (props) => {
    return <input value="first" ref={props.firstRef} />
}

class Second extends React.Component {
    render() {
        return <input value="second" ref={this.props.secondRef} />;
    }
}

export default class Test extends React.Component {

    handleSubmit = () => {
        console.log(this.first);
        // <input value="first">
        console.log(this.second)
        // <input value="second">
    }

    render() {
        return (
            <div>
                <First firstRef={(input) => { this.first = input }} />
                <Second secondRef={(input) => { this.second = input }} />
                <button onClick={this.handleSubmit}>submit</button>
            </div>
        )
    }
}
複製代碼

這種方法的本質是,將建立的ref做爲props傳遞給React組件,React組件的某個HTML元素的ref接收這個屬性,父元素即可獲取到子組件中具體的某一個HTML元素的底層DOM。這樣作,破壞了組件的封裝性,可是有時萬不得已只能這麼作了。

總結

React提供ref的初衷是給開發者一個可獲取實際DOM的工具,目前,咱們也能夠經過ref獲取子組件(React組件),可是,React仍是提倡不要過分使用ref,,畢竟有了state,props,咱們已經從各類繁雜的DOM操做中解放出來。在工做中,有時候設計到動畫,或者媒體播放器,input聚焦時,我會使用如下ref,確實解決了state和props沒法解決的問題。

我只是總結能夠如何使用ref,並無細細瞭解ref的原理,找個機會但願能夠一探究竟,又當了一次搬運工。

參考資料

Refs and the DOM

Forwarding Refs

相關文章
相關標籤/搜索