Refs 和 DOM


Refs 提供了一种访问在 render 方法中创建的 DOM 节点或 React 元素的方法。

在典型的 React 数据流中,props是父组件与其子组件交互的唯一方式。要修改一个孩子,你可以用新的道具重新渲染它。但是,在某些情况下,您需要在典型数据流之外强制修改子项。要修改的子元素可以是 React 组件的实例,也可以是 DOM 元素。对于这两种情况,React 都提供了一个逃生口。

何时使用 Refs

refs 有一些很好的用例:

  • 管理焦点、文本选择或媒体播放。
  • 触发命令式动画。
  • 与第三方 DOM 库集成。

避免将 refs 用于任何可以以声明方式完成的事情。

例如,不是在组件上公开open()close()方法,而是将prop 传递给它。Dialog``isOpen

不要过度使用 Refs

您的第一个倾向可能是在您的应用程序中使用 refs 来“让事情发生”。如果是这种情况,请花点时间更批判地思考在组件层次结构中应该拥有状态的位置。通常,很明显“拥有”该状态的适当位置位于层次结构中的更高级别。有关此示例,请参阅Lifting State Up指南。

笔记

以下示例已更新为使用React.createRef()React 16.3 中引入的 API。如果您使用的是早期版本的 React,我们建议使用回调 refs

创建参考

Refs 是使用属性创建React.createRef()并附加到 React 元素的。refRefs 通常在构建组件时分配给实例属性,以便可以在整个组件中引用它们。

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

访问参考

当 ref 被传递给 in 中的元素时render,对节点的引用可以在currentref 的属性中访问。

const node = this.myRef.current;

ref 的值因节点的类型而异:

  • 当在refHTML 元素上使用该属性时ref,构造函数中的 created withReact.createRef()接收底层 DOM 元素作为其current属性。
  • 当在ref自定义类组件上使用该属性时,该ref对象接收该组件的已安装实例作为其current.
  • 您不能ref在函数组件上使用该属性,因为它们没有实例。

下面的示例演示了这些差异。

向 DOM 元素添加 Ref

此代码使用 aref来存储对 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 将current在组件挂载时将属性分配给 DOM 元素,并在组件卸载时将其分配回nullref更新发生在生命周期componentDidMount方法之前componentDidUpdate

向类组件添加 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 MyFunctionComponent() {  return <input />;
}

class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();  }
  render() {
    // This will *not* work!
    return (
      <MyFunctionComponent ref={this.textInput} />    );
  }
}

如果你想让人们把一个ref组件带到你的函数中,你可以使用forwardRef(可能与 结合useImperativeHandle),或者你可以将组件转换为一个类。

但是,只要您引用 DOM 元素或类组件,您就可以在函数组件中**使用该属性:ref**

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

将 DOM Refs 暴露给父组件

在极少数情况下,您可能希望从父组件访问子组件的 DOM 节点。通常不建议这样做,因为它会破坏组件封装,但它有时对触发焦点或测量子 DOM 节点的大小或位置很有用。

虽然您可以将 ref 添加到子组件,但这不是一个理想的解决方案,因为您只会获得组件实例而不是 DOM 节点。此外,这不适用于功能组件。

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

如果您使用 React 16.2 或更低版本,或者如果您需要比 ref 转发提供的更多灵活性,您可以使用这种替代方法并将 ref 作为不同命名的 prop 显式传递。

如果可能,我们建议不要暴露 DOM 节点,但它可能是一个有用的逃生口。请注意,这种方法需要您向子组件添加一些代码。如果您完全无法控制子组件的实现,您的最后一个选择是使用findDOMNode(),但在StrictMode.

回调参考

React 还支持另一种设置 refs 的方法,称为“回调 refs”,它可以更细粒度地控制何时设置和取消设置 refs。

不是传递由ref创建的属性createRef(),而是传递一个函数。该函数接收 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 会ref在组件挂载时使用 DOM 元素调用回调,并在组件null卸载时调用它。componentDidMountRefs 保证在或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将其 ref 回调作为inputRefprop 传递给CustomTextInputCustomTextInput并将相同的函数作为特殊ref属性传递给<input>. 因此,this.inputElementinParent将被设置为<input>CustomTextInput.

旧版 API:字符串引用

如果您之前使用过 React,您可能会熟悉一个较旧的 API,其中ref属性是字符串,例如"textInput",并且 DOM 节点以this.refs.textInput. 我们建议不要这样做,因为字符串引用存在一些问题,被认为是遗留问题,并且可能会在未来的某个版本中被删除

笔记

如果您当前this.refs.textInput用于访问 refs,我们建议您使用回调模式createRefAPI

回调参考的注意事项

如果ref回调被定义为内联函数,它将在更新期间被调用两次,第null一次使用 DOM 元素,然后再次使用 DOM 元素。这是因为每次渲染都会创建一个新的函数实例,所以 React 需要清除旧的 ref 并设置新的。您可以通过将回调定义为类的绑定方法来避免这种ref情况,但请注意,在大多数情况下这无关紧要。


原文链接:https://codingdict.com/