React 进阶 -- Refs and DOM

React 数据流是单向的,通过 props 由父组件向子组件传递数据,如果要修改子组件,需要修改 props 来重新渲染子组件。refs 就是另一种方法,它允许我们访问真实的 DOM 节点或在 render 方法中创建的 React 元素。

何时使用

  • 管理焦点,文本选择或媒体播放。
  • 触发强制动画。
  • 集成第三方 DOM 库。

请勿过度使用 Refs,尽可能少地操作 DOM。

使用 Refs

  • 利用 React.createRef() 构建:

    export default class App extends Component {
      render() {
        return <Hello />;
      }
    }
    
    class Hello extends Component {
      constructor(props) {
        super(props);
        this.myRef = React.createRef(); // 创建 Refs
      }
    
      render() {
        return (
          <div style={{ margin: "0 auto", width: "80px" }}>
            <h1 ref={this.myRef}>Hello</h1> {/*通过 ref 属性附加到 React 元素*/}
          </div>
        );
      }
    }
    
  • 回调形式的 refs

    export default class App extends Component {
      render() {
        return <Hello />;
      }
    }
    
    class Hello extends Component {
      constructor(props) {
        super(props);
        this.myRef = null; // 创建 Refs
      }
    
      render() {
        return (
          <div style={{ margin: "0 auto", width: "80px" }}>
            <h1 ref={(element) => this.myRef = element}>Hello</h1> {/*通过 ref 属性附加到 React 元素*/}
          </div>
        );
      }
    }	
    

访问 Refs

const node = this.myRefs.current; // 将会得到 <h1> 标签

当使用回调形式的 refs 时无 current 属性,this.myRefs 即为 DOM。可通过下面的形式定义出 current,可简单理解为利用 React.createRef() 构建的 refs 即是这种原理(实际上官方还做了密封处理):

class Hello extends Component {
  constructor(props) {
    super(props);
    this.myRef = {
      current: null
    }; // 创建 Refs
  }
  
  render() {
    return (
      <div style={{ margin: "0 auto", width: "80px" }}>
        <h1 ref={this.myRef}>Hello</h1> {/*通过 ref 属性附加到 React 元素*/}
      </div>
    );
  }
}	

ref 的值根据节点的类型而有所不同:

  • ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性。
  • ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性。
  • 你不能在函数组件上使用 ref 属性,因为它们没有实例。

React 会在组件挂载时给 current 属性传入 DOM 元素,并在组件卸载时传入 null 值。ref 会在 componentDidMountcomponentDidUpdate 生命周期钩子触发前更新。

何时获取到DOM

操作 DOM

class Hello extends Component {
...
  componentDidMount() {
    if (this.myRef && this.myRef.current) {
    this.myRef.current.innerHTML = "World"; // 原本 <h1> 标签中的 Hello 将被替换为 World
    }
  }
...
}

export default Hello;

为 DOM 元素添加 ref

import React from "react";

export default class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    // 创建一个 ref 来存储 textInput 的 DOM 元素
    this.textInput = React.createRef();
    this.focusTextInput = this.focusTextInput.bind(this);
  }

  focusTextInput() {
    // 直接使用原生 API 使 text 输入框获得焦点
    // 注意:我们通过 "current" 来访问 DOM 节点
    this.textInput.current.focus();
  }

  render() {
    // 告诉 React 我们想把 <input> ref 关联到
    // 构造器里创建的 `textInput` 上
    return (
      <div>
        <input type='text' ref={this.textInput} />
        <input
          type='button'
          value='Focus the text input'
          onClick={this.focusTextInput} {/*每当点击按钮输入框将自动获得焦点*/}
        />
      </div>
    );
  }
}

为类组件添加 Ref

class AutoFocusTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();
  }

  componentDidMount() {
    this.textInput.current.focusTextInput(); //挂载后调用 current 内部方法实现输入框自动获取到焦点
  }

  render() {
    return (
      <CustomTextInput ref={this.textInput} />
    );
  }
}