React 之理解 Ref (一)

130 阅读3分钟

Refs 和 DOM

场景:

  1. 访问 DOM 元素
  2. 访问子组件中的 DOM 元素或方法

使用方法

1. 创建 Refs

// Class 组件创建 ref
this.myRef  = React.createRef()

// Function 组件创建 ref (使用 hooks 创建)
const myRef = useRef()

2. 绑定 Refs

通过 ref 属性:

  • 绑定到 DOM 元素上
  • 绑定到自定义 Class 组件上
  • 默认不能绑定到 Function 组件上,因为它没有实例(可以使用 forwardRef 实现)
<div ref = {myRef}></div>

// MyComponent为class组件
<MyComponent ref = {myRef}/>

3. 访问 Refs

  • 绑定到 React 元素上时,通过 ref 的 current 属性,访问绑定的 DOM 节点
  • 绑定到 Class 组件上时,ref 的 current 属性,指向组件的挂载实例,可以访问子组件内部的方法
this.myRef.current.focus();

4. ref 创建时机

  • 组件挂载时,current 属性指向绑定的 DOM 元素;卸载时指向 null。
  • 在 componentDidMount 或 componentDidUpdate 触发前,React 会保证 refs 一定是最新的

5. 在父组件中引用子组件的 DOM 节点

  • React 官方是不建议暴露 DOM 节点的,因为它会打破组件的封装,但它偶尔可用于触发焦点或测量子 DOM 节点的大小或位置
  • 前面所说在子组件上添加 ref(class 组件)是可以访问到组件实例(以及实例中的方法),但是不能访问到子组件的 DOM 节点
  • 此时可以使用 ref 转发,暴露子组件的 ref(见第二部分),或使用下面的回调 ref 也可以

↓↓↓

回调 Refs

1. 基本使用

不同于传递 createRef() 创建的 ref 属性,你会传递一个函数。这个函数中接受 React 组件实例或 HTML DOM 元素作为参数,以使它们能在其他地方被存储和访问。

class CustomTextInput extends React.Component{
  constructor(props) {
    super(props);

    /**
     * 方式1. createRef 创建 ref
     */
    this.ref1 = React.createRef();

    /**
     * 方式2. 创建 ref 回调函数
     * 搭配实例属性 textInput
     * 参数为 DOM 元素或组件实例
     */
    this.ref2 = (element) => {
      this.textInput = element;
    };
    this.textInput = null;

    /**
     * 方式3. √
     * 可以不创建 ref 回调函数
     * 只需要实例属性 input
     */
    this.input = null;
  }

  handleClick = () => {
    // 方式1
    this.ref1.current.focus();
    // 方式2
    this.textInput.focus(); // 不需要current了
    // 方式3
    this.input.focus(); // 不需要current了
  };

  render() {
    return (
      <div className='selectdate'>
        <input type='text' ref={this.ref1} />
        <input type='text' ref={this.ref2} />
        <input type='text' ref={(el) => (this.input = el)} />
        <input type='button' onClick={this.handleClick} value='click'/>
      </div>
    );
  }
}

2. 在组件间传递回调形式的 refs

下面在方式 3 的基础上,进行改造~

class CustomTextInput extends React.Component{
  constructor(props) {
    super(props);
    this.input = null;
  }

  handleClick = () => {
    this.input.focus();
  };

  render() {
    return (
      <div className='selectdate'>
        <CustomTextInput inputRef={(el) => (this.input = el)} />
        <input type='button' onClick={this.handleClick} value='click'/>
      </div>
    );
  }
}
// 子组件
function CustomTextInput(props) {
  return (
    <div>
      <input ref={props.inputRef} />
    </div>
  );
}

  • 理解:
  1. 父组件把它的 refs 回调函数当作 inputRef props 传递给子组件
  2. 子组件通过 props 拿到了该函数,并赋值给 ref 属性,即子组件实现了绑定回调 refs
  3. 所以在父组件中的 this.input 会被设置为与子组件中的 input 元素相对应的 DOM 节点。

总结一下

  1. 函数式子组件不能绑定 ref 。但函数式子组件用了 ref 转发,就可以了。
  2. class 子组件绑定 ref 后,父组件可以调用子组件内部方法,但是拿不到子组件 DOM 节点。
    • 但子组件用了 ref 转发,就可以拿到了。
    • 父组件使用回调 Refs,也可以拿到子组件 DOM 节点

第二部分:Refs 转发

未完~