React中的Refs

2,971 阅读3分钟

用处

修改子组件、或获取组件的属性值

在典型的React数据流中, props是父组件和子组件交互的唯一方式,要修改一个子组件,你需要使用新的props来重新渲染它。但是在某些情况下,你需要在典型的数据流之外强制修改子组件、或获取组件的属性值。被修改的子组件可能是一个React组件的实例,也可能是一个原生DOM元素。

Ref的三种创建方式

  1. React.createRef()

    在react的16.3及以后的版本中,可以在实例的构造函数中使用React.createRef()方法来创建ref, 并将其赋值给实例对象的自定义属性,以便于在整个组件中都可以使用, 然后再将其附加给原生HTML元素或Class类组件的ref属性上。

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }
  render() {
    return <div ref={this.myRef} />;
  }
}

当 ref 被传递给 render 中的元素时,对该节点的引用可以在 ref 的 current 属性中被访问。current 属性值因附加在的节点类型不同而有所不同:

当ref附加在原生HTML元素上时, current属性值为该原生DOM对象;

当ref附加在Class类组件上时, current属性值为该组件的实例对象;

ref不能使用在函数组件上,因为函数组件没有实例对象

class Component extends React.Component {
  constructor(props) {
    super(props);
    this.domRef = React.createRef();
    this.classRef =  React.createRef();
  }
  render() {
    console.log("RefComponent", this.domRef, this.classRef)
    return <div>
      	<div ref={this.domRef}> <div/>
        <MyComponent ref={this.classRef} />
      <div/>;
  }
}

image-20200404174317196

React在组件挂载时给current属性传入参数对象,在组件卸载时current属性传入null,在componentDidMount 和 componentDidUpdate触法前更新

  1. 回调函数方式

React 也支持另一种设置 refs 的方式,称为“回调 refs”。它能助你更精细地控制何时 refs 被设置和解除。

不同于传递createRef()创建的ref属性,“回调 refs”传递一个函数,并接受组件的实例对象或原生HTML DOM对象作为参数,并将该参数赋值给组件实例对象的自定义属性,以便于在整个组件中都可以使用。

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.setMyRef = null;
  }
  render() {
    return < div ref={ (el) => this.setMyRef = el } />;
  }
}

React在组件挂载时会调用refs回调函数并传入参数对象,在组件卸载时再次调用回调函数传入null,在componentDidMount 和 componentDidUpdate触法前更新;

  1. 字符串方式

    直接设置ref属性为一个字符串, 通过this.refs获取

const element = <div ref="myRef">string ref</div>
const node = this.refs.myRef

string 类型的 refs 存在一些问题, 该用法已过时

  1. 需要React去保持跟踪当前渲染的组件,有点拖累性能

  2. 当使用render callback模式渲染子组件时, 为子组件设置ref 在父组件中获取不到

class MyComponent extends Component {
  renderRow = () => {
    // 字符串设置的ref会被绑定到子组件DataTable上而不是父组件MyComponent
    return <input ref="input" />;
  }
  render() {
    return <DataTable renderRow={this.renderRow} />
  }
}
//打印发现虽然在父组件中设置的ref, 但却不是绑定在父组件中,而是绑定在子组件中的refs中, 
//如果通过回调函数设置,就可以避免该问题, 

注意点

我们说不可以在函数组件上使用ref, 因为函数组件没有实例。如果非要在函数组件上使用ref,有两个办法:

  • 将函数组件转为class类组件

  • 使用forwardRef()生成函数组件

    const FancyButton = React.forwardRef((props, ref) => {
    	return <button ref={ref} >{props.children}</button>
    })
    // 将ref作为React.forwardRef()函数的第二个参数传入,这样就可以在父组件中暴露子组件中的DOM ref
    

内联式的回调函数写法,在组件更新时会执行调用两次, 因为组件在更新后会生成新的实例对象(this),ref需要先清空旧值时调用一次,再赋值新值时又调用一次。