关于Refs的更多内容

在从 render 方法中返回 UI 结构之后,你可能想冲出 React 虚拟 DOM 的限制,在 render 返回的组件实例上调用某些方法。通常来说,这样做对于应用中的数据流动是不必要的,因为活跃的数据( Reactive data )流总是确保最新的 props 被传递到每一个从 render() 输出的子级中去。然而,仍然有几个场景使用这种方式是必须的,或者说是有益的。

考虑这样的场景:让 <input /> 元素(存在于实例的子层级中)在值被设为空字符串 '' 之后获得焦点。

  var App = React.createClass({
    getInitialState: function() {
      return {userInput: ''};
    },
    handleChange: function(e) {
      this.setState({userInput: e.target.value});
    },
    clearAndFocusInput: function() {
      this.setState({userInput: ''}); // Clear the input
      // We wish to focus the <input /> now!
    },
    render: function() {
      return (
        <div>
          <div onClick={this.clearAndFocusInput}>
            Click to Focus and Reset
          </div>
          <input
            value={this.state.userInput}
            onChange={this.handleChange}
          />
        </div>
      );
    }
  });

注意,在示例中,我们想“告诉” input 元素一些东西 - 这些东西过会儿之后已无法从它的 props 中推算出来。在这个场景中,我们想“通知” input 元素,现在应该处于获取焦点状态。然而,此处有一些障碍。从 render() 中返回的内容并不是你实际创建的组件的组合,仅仅是一个某一刻某个组件实例的 描述 - 一个快照。

注意:

记住,从 render() 中返回的内容并不是实际渲染出来的子组件实例。从 render() 返回的仅仅是子组件层级树实例在特定时间的一个描述

这意味着千万不要紧紧抓住 render() 返回的东西不放,然后还一厢情愿地希望生成自己想象中的东西。

  // counterexample: DO NOT DO THIS!
  render: function() {
    var myInput = <input />;          // I'm going to try to call methods on this
    this.rememberThisInput = myInput; // input at some point in the future! YAY!
    return (
      <div>
        <div>...</div>
        {myInput}
      </div>
    );
  }

在这个反例中,<input /> 仅仅是一个 <input /> 组件的描述。该描述用于创建一个真正的 <input />支撑实例( backing instance )

所以,我们如何与真正的 input 支撑实例( backing instance ) 交流?

ref 属性 #

React 支持一种非常特殊的属性,你可以用来绑定到 render() 输出的任何组件上去。这个特殊的属性允许你引用 render() 返回的相应的支撑实例( backing instance )。这样就可以确保在任何时间总是拿到正确的实例。

做法很简单:

1、绑定一个 ref 属性到 render 返回的东西上面去,例如:

  <input ref="myInput" />

2、在其它代码中(典型地事件处理代码),通过 this.refs 获取支撑实例( backing instance ),就像这样:

  this.refs.myInput

你可以通过调用 this.refs.myInput.getDOMNode() 直接获取到组件的 DOM 节点。

完整示例 #

  var App = React.createClass({
    getInitialState: function() {
      return {userInput: ''};
    },
    handleChange: function(e) {
      this.setState({userInput: e.target.value});
    },
    clearAndFocusInput: function() {
      // Clear the input
      this.setState({userInput: ''}, function() {
        // This code executes after the component is re-rendered
        this.refs.theInput.getDOMNode().focus();   // Boom! Focused!
      });
    },
    render: function() {
      return (
        <div>
          <div onClick={this.clearAndFocusInput}>
            Click to Focus and Reset
          </div>
          <input
            ref="theInput"
            value={this.state.userInput}
            onChange={this.handleChange}
          />
        </div>
      );
    }
  });

在这个例子中, render 函数返回一个 <input /> 实例的描述。但是真正的实例通过 this.refs.theInput 获取。只要 render 返回的某个子组件带有 ref="theInput"this.refs.theInput 就会获取到正确的实例。这甚至对于更高层的( 非 DOM )组件生效,例如 <Typeahead ref="myTypeahead" />

总结 #

Refs 是一种给指定的子组件实例发送消息的很好的方式,从某种程度上来看,通过 propsstate 来做这件事倒显得不太方便。 Refs are a great way to send a message to a particular child instance in a way that would be inconvenient to do via streaming Reactive props and state. They should, however, not be your go-to abstraction for flowing data through your application. By default, use the Reactive data flow and save refs for use cases that are inherently non-reactive.

优点: #

  • 可以在组件类里面定义任何公共的方法(比如在输入之前的重置方法),然后通过 refs 来调用这些公共的方法(比如 this.refs.myTypeahead.reset() )。
  • 管理 DOM 几乎总是需要冲出“本地”组件的限制,比如通过 this.refs.myInput.getDOMNode() 获取 <input /> 元素的底层 DOM 节点。 Refs 是做这件事唯一可靠的方式。
  • Refs 是被自动管理的!如果某个子级实例被销毁了,它的 ref 也会自动销毁。不用考虑内存问题(除非你自己做一些疯狂的操作,保存了什么引用)。

当心: #

  • 绝不要在任何组件的 render 方法中访问 refs - 或者在某个组件的 render 方法正在调用堆栈中运行的时候。
  • If you want to preserve Google Closure Compiler Crushing resilience, make sure to never access as a property what was specified as a string. This means you must access using this.refs['myRefString'] if your ref was defined as ref="myRefString".
  • If you have not programmed several apps with React, your first inclination is usually going to be to try to use refs to "make things happen" in your app. If this is the case, take a moment and think more critically about where state should be owned in the component hierarchy. Often, it becomes clear that the proper place to "own" that state is at a higher level in the hierarchy. Placing the state there often eliminates any desire to use refs to "make things happen" – instead, the data flow will usually accomplish your goal.