网络埋伏纪事

React 初学者教程 14:在 React 中访问 DOM 元素

网络埋伏纪事 · 2016-10-28翻译 · 1863阅读 原文链接

有时我们需要直接访问 HTML 元素上的属性和方法。在 React 的缤纷世界里,JSX 代表纯粹的标记,那么为什么你会想要直接处理可怕的 HTML 呢?因为你会发现,很多时候直接通过 JavaScript DOM API 处理 HTML 元素,比用 React 的方式更简单。

为强调这些情况中的一个,看看 colorizer 示例:(view in separate window):

<iframe id="I" border="0" frameborder="0" name="I2" src="https://www.kirupa.com/react/examples/colorizer.htm" marginheight="0" marginwidth="0"></iframe>

Colorizer 会用你提供的颜色给当前白色正方形上色。要看到它起作用,请在文本框中输入颜色值,点击 Go 按钮。如果不知道要输入什么颜色,就填 yellow 好了。一旦你提供了一个颜色,并提交了,白色正方形就会变成你提供的颜色:

这种正方形的颜色改变为你提供的有效颜色值是很棒的,但它并非是我想让你关注的。我要你关注的是,在你提交一个值后的文本框和按钮。注意按钮获得了焦点,你刚提交过的颜色值依然显示在表单内。如果你要输入另一个颜色值,需要显式让焦点回到文本框,清除当前存在的值。这看起来是不必要的,从可用性的角度看,我们能做得更好!

现在,如果在提交了颜色后,能立即清除已有的颜色,并且将焦点返回到文本框,是不是更好?这意味着如果我们提交了一个 purple 颜色值,你所看到的会是如下这样:

输入的值 purple 被清除,焦点返回到文本框。

要实现这种行为,用 JSX 和传统的 React 技术是很难的,我们甚至不打算为解释如何去做而费神。通过直接在不同 HTML 元素上处理 JavaScript DOM API 而执行这种行为是很简单的。猜猜我们要做什么?在如下的小节中,我们打算使用 refs。React 提供 refs 来帮助我们访问 HTML 元素上的 DOM API。

初始 refs

现在你已经很清楚,在各种 render 方法内,我们写的是 JSX。JSX 只是一个对 DOM 看起来应该像什么的一个描述。它不代表实际的 HTML,虽然它看起来很像。不过,为在 JSX 和最终在 DOM 中的 HTML 元素之间提供一个桥梁,React 为我们提供了 refs(references 的简写)。

refs 的工作方式有点古怪。搞清楚它的最简单方式是看一个示例。假如 colorizer示例中的render` 方法是如下:

render: function() {
    var squareStyle = {
        backgroundColor: this.state.bgColor
    };

    return (
        <div>
            <div></div>
            &lt;form onSubmit={this.setNewColor}&gt;
                &lt;input onChange={this.colorValue}
                    placeholder="Enter a color value"&gt;
                &lt;/input&gt;
                &lt;button type="submit"&gt;go&lt;/button&gt;
            &lt;/form&gt;
        </div>
    );
}

render 方法内,我们返回一大段 JSX,用来表示我们要输入颜色值的 input 元素(还有其它东西)。我们想做的是访问 input 元素的 DOM 表示,这样就可以用 JavaScript 访问它上面的其它 API。

refs 做这事的方法是,在要引用 HTML 的元素上设置 ref 属性:

render: function() {
    var squareStyle = {
        backgroundColor: this.state.bgColor
    };

    return (
        <div>
            <div></div>
            &lt;form onSubmit={this.setNewColor}&gt;
                &lt;input ref={}
                    onChange={this.colorValue}
                    placeholder="Enter a color value"&gt;
                &lt;/input&gt;
                &lt;button type="submit"&gt;go&lt;/button&gt;
            &lt;/form&gt;
        </div>
    );
}

因为我们只是对 input 元素有兴趣,所以 ref 属性就放在它上面。现在,ref 属性是空的。通常我们会把 ref 属性的值设置为一个 JavaScript 回调函数。这个函数会在拥有这个 render 方法的组件挂载时自动被调用。如果你将 ref 属性的值设置为存储了对被引用的 DOM 元素的引用的简单 JavaScript 函数,它会像如下高亮度行:

render: function() {
  var squareStyle = {
    backgroundColor: this.state.bgColor
  };

  var self = this;

  return (
    <div>
      <div></div>

      &lt;form onSubmit={this.setNewColor}&gt;
        &lt;input
            ref={
                  function(el) {
                    self._input = el;
                  }
                }
            onChange={this.colorValue}
            placeholder="Enter a color value"&gt;
        &lt;/input&gt;
        &lt;button type="submit"&gt;go&lt;/button&gt;
      &lt;/form&gt;
    </div>
  );
}

组件挂载后,这段代码运行的最终结果很简单:我们可以通过调用 this._input,在组件内任何地方访问表示 input 元素的 HTML。花点时间看看高亮度代码行是如何帮助做到这点的。看完后,我们将整体看看这段代码。

首先,回调函数看起来是这样的:

function(el) {
 self._input = el;
}

这个匿名函数在组件挂载时被调用,对最终 HTML DOM 元素的一个引用被作为参数传递进来。这里我们是用 el 标识符捕获这个参数,但是你可以用你想用的任何名称来给这个参数命名。这个回调函数的函数体只是将一个自定义的属性 _input 设置为 DOM 元素的值。为确保在组件上创建这个属性,我们用 self 变量来创建一个闭包,这里 this 毫无疑问引用的是我们的组件,而不是回调函数本身。

我们看看 Colorizer 组件的完整代码,与 ref 相关的用高亮度行标出:

var Colorizer = React.createClass({
    getInitialState: function() {
      return {
          color: '',
          bgColor: ''
      }
    },
    colorValue: function(e) {
      this.setState({color: e.target.value});
    },
    setNewColor: function(e){
      this.setState({bgColor: this.state.color});

      this._input.value = "";
      this._input.focus();

      e.preventDefault();
    },
    render: function() {
      var squareStyle = {
        backgroundColor: this.state.bgColor
      };

      var self = this;

      return (
        <div>
          <div></div>

          &lt;form onSubmit={this.setNewColor}&gt;
            &lt;input
                ref={
                      function(el) {
                        self._input = el;
                      }
                    }
                onChange={this.colorValue}
                placeholder="Enter a color value"&gt;
            &lt;/input&gt;
            &lt;button type="submit"&gt;go&lt;/button&gt;
          &lt;/form&gt;
        </div>
      );
    }
});

只关注一下 input 元素发生了什么,当表单被提交,并且 setNewColor 方法被调用时,我们通过调用 this._input.value = “”,来清除 input 元素的内容。通过调用 this._input.focus() ,把焦点设置到 input 元素上。所有与 ref 相关的工作,只是让这两行能够运行。而这两行里,我们需要一些方法,让 this._input 指向在 JSX 中定义的代表 input 元素的 HTML 元素。一旦我们搞清楚了这个,就可以调用 DOM API 暴露在这个元素上的 value 属性 和 focus 方法。

> 用 ES6 箭头函数进一步简化 > 学习 React 是很难,所以我试图避免强制你默认使用 ES6 技术。到了使用 ref 属性的时候,使用箭头函数处理回调函数,确实会简化一点。这也是我推荐使用 ES6 技术的情景之一。 > 如同之前所看到的,要把组件上的一个属性赋值给被引用的 HTML 元素,我们像这样写代码: >

&lt;input
    ref={
          function(el) {
            self._input = el;
          }
        }
    onChange={this.colorValue}
    placeholder="Enter a color value"&gt;
&lt;/input&gt;

> 为处理作用域的鬼把戏,我们创建了一个 self 变量,初始化为 this,以确保是在我们的组件上创建 _input 属性。这看起来不必要地混乱。 > 使用箭头函数,我们可以把代码简化成如下这样: >

&lt;input
    ref={(el) =&gt; this._input = el}
    onChange={this.colorValue}
    placeholder="Enter a color value"&gt;
&lt;/input&gt;

> 最终结果与前面是一致的,并且因为箭头函数处理作用域的机制,你可以在函数体内使用 this 来引用组件,而不需要做额外的工作。不再需要一个外部的 self 变量相等物!

总结

在本教程中,我们看到直接访问 DOM 是如何 “简单” 。React 过去提供了相当简单的方式引用元素,就是在元素上设置 refs 属性,并初始化它为一个字符串值:

&lt;button refs="myButton"&gt;Click me!&lt;/button&gt;

然后,在组件挂载之后,你就可以通过做点像 this.refs.myButton 这样的事情来访问该元素。但是先不要激动,这种基于字符串的方法已经被废弃了。在本文编写的时候还能用,但是谁知道以后能不能用呢?

相关文章