React高级指南--引用Refs和DOM

引用Refs提供了一个访问render()方法DOM节点或者React元素的方法。

典型的React数据流中,props是双亲组件和子组件交互的唯一手段。要修改一个子组件,你需要使用新的props重新渲染它。然而,确实存在少数一些情况你需要命令性地(imperatively)修改一个子节点而不是通过典型的props数据流方式。被修改的子节点可能是一个React组件实例,亦或是一个DOM元素。对于这两种情况,React都提供了应急处理方案(escape hatch)。

何时使用引用Refs?

有一些场景很适合使用refs

  • 管理焦点,文本选择,或者媒体播放
  • 触发命令性动画
  • 和第三方DOM库集成

除此之外,如果能通过声明方式解决问题,尽量避免使用refs
比如,一个Dialog组件有open(),close()暴露出来可用的方法,但是尽量不要去使用它们,而是通过声明的方式传递一个isOpen这样的属性给它来控制该对话框的打开和关闭。

不要滥用引用Refs

你的第一倾向可能是在你的应用中使用refs“先把事情搞定”。如果是这样,还请三思,整个组件层级结构中,状态到底应该在哪里被管理呢?很明显,”拥有”(own)属性的合适位置通常都是层级结构中更高层的一些地方。看看这篇文章”状态提升(Lifting State Up)“,有一些相关的例子。

注意:
下面的例子更新了,使用了Ract 16.3引入的API React.createRef()。如果你使用的比较旧的React版本,我们建议你使用refs回调方式(译注:本文后面部分有讲述)。

创建引用Refs

引用Refs通过React.createRef()创建,通过属性ref附加到React元素上。Refs通常在一个组件构造时赋值给一个实例属性,这样在整个组件中他们都可以被引用到。例子:

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }
  render() {
    return <div ref={this.myRef} />;
  }
}

访问引用Refs

render方法中,当一个引用ref被传递给一个元素,在refcurrent属性中就会出现对该节点的一个引用:

// 继续了上面的例子,目前 node 就指向了上面返回的DOM div 节点
const node = this.myRef.current;

根据节点类型的不同,ref的值也不同:

  • 如果ref用在HTML元素上,构造函数中通过React.createRef()创建的ref会将底层DOM元素放到它的current属性中。
  • 如果ref用在自定义组件类型上,ref使用它的current属性指向所挂载的组件实例。
  • 功能性组件上不能使用ref,因为它们没有实例。
    下面的例子演示了这些不同。

为DOM元素增加Ref引用

下面的代码使用一个ref保存对一个DOM节点的引用:

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>
    );
  }
}

组件挂载时,React会将refcurrent属性设置成DOM元素,卸载时,再把refcurrent属性设置为nullref更新发生在componentDidMount或者componentDidUpdate生命周期回调之前。

扫描二维码关注公众号,回复: 149615 查看本文章

向类组件增加引用Ref

如果我们想封装上面的CustomTextInput组件模拟它挂载后立即被点击的事件,我们可以使用引用ref访问这个自定义输入框并手动调用它的focusTextInput方法:

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

  componentDidMount() {
    this.textInput.current.focusTextInput();
  }

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

注意,这种方式仅在CustomTextInput被定义为一个类的情况下才工作:

class CustomTextInput extends React.Component {
  // ...
}

引用Refs和功能性组件

功能性组件上不能使用ref因为它们没有实例:

function MyFunctionalComponent() {
  return <input />;
}

class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();
  }
  render() {
    // 不工作!不工作!不工作!
    return (
      <MyFunctionalComponent ref={this.textInput} />
    );
  }
}

这种情况下,如果你要是用ref,需要将该组件转换成一个类组件,就跟你需要生命周期方法或者状态时做的一样。

然而,在一个功能性组件内部你可以使用ref引用属性只要它指向一个DOM元素或者一个类组件。

function CustomTextInput(props) {
  // textInput must be declared here so the ref can refer to it
  let textInput = React.createRef();

  function handleClick() {
    textInput.current.focus();
  }

  return (
    <div>
      <input
        type="text"
        ref={textInput} />

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

暴露DOM引用给双亲组件

在很少的一些情况下,你需要从双亲组件中访问某个子DOM节点。一般来说不建议这么做,因为它打破了组件封装,但是它偶尔也很有用,比如触发获取焦点,或者测量一个子DOM节点的尺寸或者位置。

尽管你可以给子组件增加一个ref引用(译注:参考上面AutoFocusTextInputCustomTextInput的例子),但这不是一个理想的解决方案,因为你得到的只是一个组件实例而不是一个DOM节点。另外,这个方法对于功能性组件不工作。

如果你在使用React 16.3或者更高版本,我们建议在这些情况下你使用引用转发方案。引用转发允许组件选择将任何子组件的引用ref当成是自己的。你可以在引用转发文档中找到如何将子节点的DOM节点公开给父组件的详细示例。

如果你使用的是React 16.2或更低版本,或者你需要比引用转发更多的灵活性,那你可以使用这种替代方案,并显式传递引用,属性名称不必是ref

可能的话,我们建议不要公开DOM节点,但它有可能是一个有用的应急逃生舱。注意这种方法需要你向子组件添加一些代码。如果你完全不能控制子组件的实现,你最后的选择是使用findDOMNode(),另外一种不被建议使用的方案。

引用回调函数

React支持另外一种设置引用的方法叫做“引用回调函数”,该方法能对引用设置的做更多细粒度的控制。

这种方法中不再是传递一个createRef()创建的ref引用,而是传递一个函数。这个函数接收一个React组件实例或者一个HTML DOM元素作为它的参数,用于在其他地方保存或者访问。

下面的例子实现了这一通用模式:使用ref回调保存一个DOM节点的引用到一个实例属性中去。

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>
    );
  }
}

React会在组件挂载时在DOM元素上调用ref回调函数(参数就是这个DOM元素),在组件卸载时再次调用该ref回调函数,使用的参数是nullref回调调用发生在在componentDidMount或者componentDidUpdate生命周期回调方法调用之前。

你可以像传递React.createRef()创建的对象引用那样在组件之间传递引用回调函数:

function CustomTextInput(props) {
  return (
    <div>
      <input ref={props.inputRef} />
    </div>
  );
}

class Parent extends React.Component {
  render() {
    return (
      <CustomTextInput
        inputRef={el => this.inputElement = el}
      />
    );
  }
}

上面的例子中,类Parent将它的引用回调函数作为一个inputRef属性传给了子组件CustomTextInput,然后子组件CustomTextInput将同一个函数作为ref属性传给了<input>。结果就是,Parent中,this.inputElement会被设置成CustomTextInput里面相应的<input>的元素。

遗留API:字符串引用Refs

如果你之前使用React,可能会对一个比较旧的API比较熟悉,这个API中ref属性是一个字符串,像是textInput,而DOM节点通过this.refs.textInput来访问。我们现在建议不要再使用该方案,因为它有些问题,这个方案因此也已经作为遗留方案被废弃了,很可能在将来的某个版本中会被移除。

使用引用回调函数的注意事项

如果ref回调函数定义在内联函数(inline function)中,更新时他会被调用两次,第一次参数是null,第二次参数才是DOM元素。这是因为每个渲染都会创建一个新的函数实例,所以React需要清除旧的引用并设置新的。你可以通过将引用回调定义为该类的绑定方法来避免这种情况,但请注意,大多数情况下这样做或者不这样做都没太大关系。

英文原文

猜你喜欢

转载自blog.csdn.net/andy_zhang2007/article/details/80059571