React: Concept: Refs

20th August 2020 at 2:19pm

在 React 组件中,你可以创建 ref 对象使其指向一个子组件的实例或者 DOM 元素,这样你可以显式地调用它的 API 来改变它(而不是通过指令式的 props 和 state)。在 React: Concept: Form Elements 提到的 uncontrolled components 即使用了 ref。

什么情况下可以用 refs?

大多数情况下你都不应该使用 ref,因为它是命令式的(即你会调用实际的 API 去改变组件状态),而不是通过 props / state 这种指令式让 React 帮你做改变。仅在这些情况下使用它:

  • 有一些 DOM 元素的操作,无法通过 React 的指令式来改变时,比如操作 <input> 元素的 focus 状态、选中文本,或者控制媒体播放
  • 调用命令式的动画
  • 与第三方 DOM 库结合

这个例子表示什么样的过程是命令式和声明式。比如你有一个对话框组件,它默认是隐藏的,一旦你点击了开启的按钮,它会显示出来。那么:

  • 调用这个组件的 open()close() 函数来控制是否显示,即是 命令式
  • 设计一个 isOpened 变量,由 React 判断这个变量来决定是否展示对话框,即是 声明式

基本用法

在 class component 中使用 React.createRef()

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    // create a ref to store the textInput DOM element
    this.textInput = React.createRef();
    this.focusTextInput = this.focusTextInput.bind(this);
  }

  focusTextInput() {
    // Explicitly focus the text input using the raw DOM API
    // Note: we're accessing "current" to get the DOM node
    this.textInput.current.focus();
  }

  render() {
    // tell React that we want to associate the <input> ref
    // with the `textInput` that we created in the constructor
    return (
      <div>
        <input
          type="text"
          ref={this.textInput} />
        <input
          type="button"
          value="Focus the text input"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
}

在 functional component 中,使用 useRef

function CustomTextInput(props) {
  // textInput must be declared here so the ref can refer to it
  const textInput = useRef(null);
  
  function handleClick() {
    textInput.current.focus();
  }

  return (
    <div>
      <input
        type="text"
        ref={textInput} />
      <input
        type="button"
        value="Focus the text input"
        onClick={handleClick}
      />
    </div>
  );
}

Callback Refs

如果你想更好的地控制 ref 的生命周期,可以用 callback refs:

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

    this.textInput = null;

    this.setTextInputRef = element => {
      this.textInput = element;
    };

    this.focusTextInput = () => {
      // Focus the text input using the raw DOM API
      if (this.textInput) this.textInput.focus();
    };
  }

  componentDidMount() {
    // autofocus the input on mount
    this.focusTextInput();
  }

  render() {
    // Use the `ref` callback to store a reference to the text input DOM
    // element in an instance field (for example, this.textInput).
    return (
      <div>
        <input
          type="text"
          ref={this.setTextInputRef}
        />
        <input
          type="button"
          value="Focus the text input"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
}

它的重点是,将 this.setTextInputRef 传至 <input type="text" ref={this.setTextInputRef} />。React 保证:

  • componentDidMount / componentDidUpdate 前,以实际的 <input> node 作为参数调用 setTextInputRef。这样 this.textInput 便指向了 DOM 节点
  • componentDidUnmount 时(未提及前后),调用 setTextInputRef(null),从而将 this.textInput 设置为了 null

除了更好地控制 this.textInput 变量之外,我翻了一些博客文章,并没有谁提到这个能力能应用在什么地方。也许适合在 unmount 时做一些清洁工作?

传递 ref 给一个组件

非典型的使用场景。参考 React 官方文档 以及 Exposing DOM Refs to Parent Components 一节。

Reference