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

相关文章
相关标签/搜索