【译】在 React 组件中使用 Refs 指南

原文: Fullstack React’s Guide to using Refs in React Components

作者:Yomi Eluwande

译者:博轩

使用 React 时,我们的默认思维方式应该是 不会强制修改 DOM ,而是通过传入 props 重新渲染组件。但是,有些情况却无法避免修改 DOM

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

当父组件需要与子组件交互时,我们通常使用 props 来传递相关信息。 但是, 在某些情况下,我们可能需要修改子项,而不用新的 props 重新呈现 (re-rendering) 它。 这时候就需要 refs 出场了。

我什么时候应该使用 Refs ?

我们建议在以下情况下使用 refs

DOM

译注:第三点是否也可以理解为使用 event 对象呢?在 React 中就是合成事件(SyntheticEvent)。

官方文档中提到:避免使用 refs 来做任何可以通过声明式实现来完成的事情。

所以一旦我们确定我们真的应该使用 refs ,我们需要如何使用它们呢?

在 React 中使用 Refs

您可以通过多种方式使用 refs :

  • React.createRef()
  • 回调引用 (Callback refs)
  • String refs(已过时)
  • 转发 refs (Forwarding refs)

接下来,让我们看看每一种实现方式:

React.createRef()

可以使用该 React.createRef() 函数创建 Refs ,并通过该 ref 属性附加到 React 组件中的 HTML 元素。

通常在组件的构造函数内创建 ref ,使其在整个组件中可用。例如:

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.firstRef = React.createRef();
  }
  render() {
    return 
; } }

如上所示:

  • 一个 ref 实例在构造函数中创建,并赋值给 this.firstRef
  • render() 方法内部,将构造函数中创建的 ref 传递给 div

接下来,让我们看一个在 React 组件中使用 refs 的示例。

使用 Refs 聚焦输入

这是另一个例子:

// Ref.js
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  ref
    // with the `textInput` that we created in the constructor
    return (
      
); } }

示例链接

在上面的代码块中,我们构建了一个按钮,当单击它时, 该页面会自动聚焦在输入框上。

首先,我们在构造方法中创建一个 ref 实例,并将其赋值给 this.textInput ,然后通过 ref 属性将其分配给 input 元素。

注意,当 ref 属性被一个 HTML 元素使用时(比如当前示例中的 input 元素),在 constructor 中使用 React.createRef() 创建的 ref 会接收 来自底层 DOM 元素的 current 值。

译注:这里的 current 应该是 合成事件(SyntheticEvent)

这意味着访问 DOM 值,我们需要写这样的东西:

this.textInput.current;

第二个元素是一个按钮,点击它之后会自动聚焦到第一个输入框上面。我们为 onClick 属性设置了 this.focusTextInput 函数。

函数 focusTextInput() 使用了 JavaScript 构建 DOM 的标准函数。 .focus() 方法会将光标聚焦于文本输入框上。

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

最后, focusTextInput 函数绑定在这样的 constructor 方法中的:

this.focusTextInput = this.focusTextInput.bind(this);

ref 中获取值

在这个例子中,我们将看到如何为 input 输入框设置 ref 属性,并通过 ref 来获取值。示例如下:

示例链接

在这个例子中,我们创建了一个 input 输入框来输入值。然后,当单击提交按钮时,我们将读取此值,并在控制台打印。

// Ref.js
class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    // create a ref to store the textInput DOM element
    this.textInput = React.createRef();
  }
  handleSubmit = e => {
    e.preventDefault();

    console.log(this.textInput.current.value);
  };

  render() {
    // tell React that we want to associate the  ref
    // with the `textInput` that we created in the constructor
    return (
      
this.handleSubmit(e)}>
); } }

同样,我们使用该 React.createRef() 函数创建一个 ref 实例,然后将它分配给实例变量 this.textInput

render 函数中,我们希望读取 form 下输入框的值。 我们如何读取这个值? 通过为 input 指定一个 ref ,然后读取 ref 的值。

点击提交按钮,上面示例中 form 元素会通过 onSubmit 方法,调用 this.handleSubmit 函数 ,并在控制台打印输入框中的信息。

handleSubmit = e => {
  e.preventDefault();
  console.log(this.textInput);
};
上面,参数
e 包含事件对象。我们使用
e.preventDefault() 来告诉浏览器我们正在处理被点击的提交按钮,我们不希望这个事件“冒泡”(意思就是说,阻止浏览器的默认行为)。

译注:这里可以看一下 React 对于 事件的处理 :在 React 中另一个不同点是你不能通过返回 false 的方式阻止默认行为。你必须显式的使用 preventDefault

在上面示例中,我们打印了 this.textInput ,在控制台可以看到一个 ref 对象。

> Object {current: HTMLInputElement}

请注意,它有一个 current 属性,即 HTMLInputElement 。这是 input DOM 元素本身, 而不是实际值。 我们必须使用 this.textInput.current.value 来获取 input 标签的实际值:

handleSubmit = e => {
  e.preventDefault();
  console.log(this.textInput.current.value);
};

使用 refs 是一种从表单中直接提取值的方式:只需要给 input 标签设置 ref ,并在你需要的时候将值提取出来。

Refs 回调

Refs 回调是在 React 中使用 ref 的另一种方式。要以这种方式使用 ref ,我们需要为 ref 属性设置回调函数。当我们设置 ref 时, React 会调用这个函数,并将 element 作为第一个参数传递给它。

示例链接

这是另一个例子的代码。像上面的示例一样,此代码获取 input 标签的文本值,但在这里我们使用回调引用:

// Refs.js
class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = null;

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

  handleSubmit = e => {
    e.preventDefault();
    console.log(this.textInput.value);
  };

  render() {
    return (
      
this.handleSubmit(e)}>
); } }

上面的示例中,我们将 input 标签的 ref 设置为 this.setTextInputRef

当组件安装时, React 会将 DOM 元素传递给 ref 的回调;当组件卸载时,则会传递 null 。( ref 回调会在 componentDidMountcomponentDidUpdate 生命周期之前调用。)

String Ref(已过时)

还有另一种设置 refs 的方法,但它被认为是过时的,可能很快就会被弃用。但是你可能会在其他人的代码中看到它,所以这里说一下。

使用 string refs ,你将会看到这样的 input 标签:

然后,我们可以在组建上得到这样的值: this.refs.textInput.value – 但是,再次声明,这不应该在新代码中使用,因为这个 API 将被弃用。

转发 Refs (Forwarding Refs)

Ref forwarding 是一种将 ref 通过组件传递给其子节点的技术。它对于可复用组件库和高阶组件(HOC)等情况非常有用。

示例地址

您可以使用 React.forwardRef 函数将 ref 转发到组件。我们来看下面的例子:

// Ref.js
const TextInput = React.forwardRef((props, ref) => (
  
));

const inputRef = React.createRef();

class CustomTextInput extends React.Component {
  handleSubmit = e => {
    e.preventDefault();
    console.log(inputRef.current.value);
  };

  render() {
    return (
      
this.handleSubmit(e)}>
); } }

Ref forwarding 允许组件接收一个 ref ,并将它向下传递(换句话说,“转发”它)给子组件。

在上面的示例中,我们使用 input 标签创建了一个名为 TextInput 的组件。那么,我们如何将 ref 传递或转发到 input 标签呢?

首先,我们使用下面的代码创建一个 ref

const inputRef = React.createRef();

然后,我们将 ref 通过为组件 指定一个同名的 JSX 的属性,将 ref 向下传递。然后 React 将会把 ref 作为第二个参数转发给 forwardRef 函数。

接下来,我们将此 ref 参数转发给 。现在可以在外层组件通过 inputRef.current 访问DOM节点的值了。

转发 refs 和高阶组件

最后,让我们看一下使用 refs 的另一个例子,但这次是使用高阶组件(HOC)。

示例链接

在上面的示例应用程序中,会将所有 input 标签中输入的值在控制台打印。这里已经为 input 标签设置了 ref 属性,接下来,让我们看一下需要如何在高阶组件中传递 / 转发 ref

const Input = InputComponent => {
  const forwardRef = (props, ref) => {
    const onType = () => console.log(ref.current.value);
    return ;
  };
  return React.forwardRef(forwardRef);
};

这里有一个名为 Input 的高阶组件 ,它接受 InputComponent 作为参数。当用户输入的时候,他还会将 ref 的值在控制台打印。

Input 高阶组件内, forwardRef 函数会返回 InputComponentforwardRef 函数中所包含的 ref 参数,是由 React.forwardRef 函数创建的。 高阶组件最终会将包装好的组件作为值返回。

接下来,我们创建一个组件,将 input 作为子组件包含进来。

const TextInput = ({ forwardedRef, children, ...rest }) => (
  
{children}
);

上面的组件会将 forwardedRef 分配给 ref 属性, 当渲染子组件的时候, input 输入框就会接收到这个 ref...restprops 的解构(也就是说,我们会将 rest 数组中的所有参数作为 props 传递给 input 组件)。那么我们该如何使用 TextInput 组件呢?像这样:

const InputField = Input(TextInput);

class CustomTextInput extends Component {
  render() {
    const inputRef = React.createRef();
    
    return ;
  }
}

最后,将 TextInput 传入 Input 高阶组件,会返回一个 InputField component

创建一个 ref ,并作为参数传递给 InputField 组件。

结论

与通过 propsstate 不同, Refs 是一种将数据传递给特定子实例的好方法。

你必须要小心,因为 refs 操纵实际的 DOM ,而不是虚拟的 DOM ,这与 React 思维方式相矛盾。因此,虽然 refs 不应该是通过应用程序流动数据的默认方法,但是当您需要时,它们是可以从 DOM 元素读取数据的好方法。

译注:推荐下「司徒正美」大佬的 React v16.3.0: New lifecycles and context API ,createRef API,forwardRef API 中的示例可以作为补充阅读。

本文已经联系原文作者,并授权翻译,转载请保留原文链接