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

原文: Fullstack React's Guide to using Refs in React Components
做者:Yomi Eluwande
译者:博轩

refs

使用 React 时,咱们的默认思惟方式应该是 不会强制修改 DOM ,而是经过传入 props 从新渲染组件。可是,有些状况却没法避免修改 DOMhtml

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

当父组件须要与子组件交互时,咱们一般使用 props 来传递相关信息。 可是,在某些状况下,咱们可能须要修改子项,而不用新的props 从新呈现 (re-rendering) 它。 这时候就须要 refs 出场了。react

我何时应该使用 Refs ?

咱们建议在如下状况下使用 refssegmentfault

  • 与第三方 DOM 库集成
  • 触发命令式动画
  • 管理焦点,文本选择或媒体播放
译注:第三点是否也能够理解为使用 event 对象呢?在 React 中就是合成事件(SyntheticEvent)。
官方文档中提到:避免使用 refs 来作任何能够经过声明式实现来完成的事情。

因此一旦咱们肯定咱们真的应该使用 refs,咱们须要如何使用它们呢?数组

在 React 中使用 Refs

您能够经过多种方式使用 refs :浏览器

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

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

React.createRef()

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

一般在组件的构造函数内建立 ref ,使其在整个组件中可用。例如:函数

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

如上所示:动画

  • 一个 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 <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>
    );
  }
}

示例连接

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

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

<input type="text" ref={this.textInput} />

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

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

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

this.textInput.current;

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

<input
  type="button"
  value="Focus the text input"
  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 <input> ref
    // with the `textInput` that we created in the constructor
    return (
      <div>
        <form onSubmit={e => this.handleSubmit(e)}>
          <input type="text" ref={this.textInput} />
          <button>Submit</button>
        </form>
      </div>
    );
  }
}

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

render 函数中,咱们但愿读取 form 下输入框的值。咱们如何读取这个值? 经过为 input 指定一个 ref ,而后读取 ref 的值。

<input type="text" ref={this.textInput} />

点击提交按钮,上面示例中 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 (
      <div>
        <form onSubmit={e => this.handleSubmit(e)}>
          <input type="text" ref={this.setTextInputRef} />
          <button>Submit</button>
        </form>
      </div>
    );
  }
}

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

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

String Ref(已过期)

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

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

<input type="text" ref="textInput" />

而后,咱们能够在组建上获得这样的值:this.refs.textInput.value - 可是,再次声明,这不该该在新代码中使用,由于这个 API 将被弃用。

转发 Refs (Forwarding Refs)

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

示例地址

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

// Ref.js
const TextInput = React.forwardRef((props, ref) => (
  <input type="text" placeholder="Hello World" ref={ref} />
));

const inputRef = React.createRef();

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

  render() {
    return (
      <div>
        <form onSubmit={e => this.handleSubmit(e)}>
          <TextInput ref={inputRef} />
          <button>Submit</button>
        </form>
      </div>
    );
  }
}

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

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

首先,咱们使用下面的代码建立一个 ref

const inputRef = React.createRef();

而后,咱们将 ref 经过为组件 <TextInput ref={inputRef}> 指定一个同名的 JSX 的属性,将 ref 向下传递。而后 React 将会把 ref 做为第二个参数转发给 forwardRef 函数。

接下来,咱们将此 ref 参数转发给 <input ref={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 <InputComponent forwardedRef={ref} onChange={onType} {...props} />;
  };
  return React.forwardRef(forwardRef);
};

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

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

接下来,咱们建立一个组件,将 input 做为子组件包含进来。

const TextInput = ({ forwardedRef, children, ...rest }) => (
  <div>
    <input ref={forwardedRef} {...rest} />
    {children}
  </div>
);

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

const InputField = Input(TextInput);

class CustomTextInput extends Component {
  render() {
    const inputRef = React.createRef();
    
    return <InputField ref={inputRef} />;
  }
}

最后,将 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 中的示例能够做为补充阅读。 本文已经联系原文做者,并受权翻译,转载请保留原文连接
相关文章
相关标签/搜索