在 React 组件中使用 Refs 指南

算法江湖客
• 阅读 98

使用 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 ,使其在整个组件中可用。例如:
[JavaScript] 纯文本查看 复制代码
?

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 聚焦输入
这是另一个例子:
[JavaScript] 纯文本查看 复制代码
?

// Ref.jsclass 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 元素。
[HTML] 纯文本查看 复制代码
?

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

注意,当 ref 属性被一个 HTML 元素使用时(比如当前示例中的 input 元素),在 constructor 中使用 React.createRef() 创建的 ref 会接收来自底层 DOM 元素的 current 值。
译注:这里的 current 应该是 合成事件(SyntheticEvent)
这意味着访问 DOM 值,我们需要写这样的东西:
[JavaScript] 纯文本查看 复制代码
?

this.textInput.current;

第二个元素是一个按钮,点击它之后会自动聚焦到第一个输入框上面。我们为 onClick 属性设置了 this.focusTextInput 函数。
[JavaScript] 纯文本查看 复制代码
?

<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}/>
函数 focusTextInput() 使用了 JavaScript 构建 DOM 的标准函数。 .focus() 方法会将光标聚焦于文本输入框上。
[JavaScript] 纯文本查看 复制代码
?

focusTextInput() {
this.textInput.current.focus();}
最后,focusTextInput 函数绑定在这样的 constructor 方法中的:
[JavaScript] 纯文本查看 复制代码
?
1
this.focusTextInput = this.focusTextInput.bind(this);
从 ref 中获取值
在这个例子中,我们将看到如何为 input 输入框设置 ref 属性,并通过 ref 来获取值。示例如下:
在这个例子中,我们创建了一个 input 输入框来输入值。然后,当单击提交按钮时,我们将读取此值,并在控制台打印。
[JavaScript] 纯文本查看 复制代码
?

// Ref.jsclass 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 的值。
[JavaScript] 纯文本查看 复制代码
?
1
<input type="text" ref={this.textInput} />
点击提交按钮,上面示例中 form 元素会通过 onSubmit 方法,调用 this.handleSubmit 函数 ,并在控制台打印输入框中的信息。
[JavaScript] 纯文本查看 复制代码
?

handleSubmit = e => {
e.preventDefault();
console.log(this.textInput);};
上面,参数 e 包含事件对象。我们使用e.preventDefault() 来告诉浏览器我们正在处理被点击的提交按钮,我们不希望这个事件“冒泡”(意思就是说,阻止浏览器的默认行为)。
译注:这里可以看一下 React 对于事件的处理:在 React 中另一个不同点是你不能通过返回 false 的方式阻止默认行为。你必须显式的使用 preventDefault
在上面示例中,我们打印了 this.textInput ,在控制台可以看到一个 ref 对象。
[JavaScript] 纯文本查看 复制代码
?
1

Object {current: HTMLInputElement}
请注意,它有一个 current属性,即 HTMLInputElement 。这是 input DOM 元素本身,而不是实际值。 我们必须使用 this.textInput.current.value 来获取 input 标签的实际值:
[JavaScript] 纯文本查看 复制代码
?

handleSubmit = e => {
e.preventDefault();
console.log(this.textInput.current.value);};
使用 refs 是一种从表单中直接提取值的方式:只需要给 input 标签设置 ref ,并在你需要的时候将值提取出来。
Refs 回调
Refs 回调 是在 React 中使用 ref 的另一种方式。要以这种方式使用 ref,我们需要为 ref 属性设置回调函数。当我们设置 ref 时,React 会调用这个函数,并将 element 作为第一个参数传递给它。
这是另一个例子的代码。像上面的示例一样,此代码获取 input 标签的文本值,但在这里我们使用回调引用:
[JavaScript] 纯文本查看 复制代码
?

// Refs.jsclass 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 回调会在 componentDidMount 和 componentDidUpdate 生命周期之前调用。)
String Ref(已过时)
还有另一种设置 refs 的方法,但它被认为是过时的,可能很快就会被弃用。但是你可能会在其他人的代码中看到它,所以这里说一下。
使用 string refs,你将会看到这样的 input 标签:
[HTML] 纯文本查看 复制代码
?
1
<input type="text" ref="textInput" />

然后,我们可以在组建上得到这样的值:this.refs.textInput.value - 但是,再次声明,这不应该在新代码中使用,因为这个 API 将被弃用。
转发 Refs (Forwarding Refs)
Ref forwarding 是一种将 ref 通过组件传递给其子节点的技术。它对于可复用组件库和高阶组件(HOC)等情况非常有用。
您可以使用 React.forwardRef 函数将 ref 转发到组件。我们来看下面的例子:
[JavaScript] 纯文本查看 复制代码
?

// Ref.jsconst 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 :
[JavaScript] 纯文本查看 复制代码
?

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 。
[JavaScript] 纯文本查看 复制代码
?

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 函数会返回 InputComponent。forwardRef 函数中所包含的 ref 参数,是由 React.forwardRef 函数创建的。 高阶组件最终会将包装好的组件作为值返回。
接下来,我们创建一个组件,将 input 作为子组件包含进来。
[JavaScript] 纯文本查看 复制代码
?

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

<input ref={forwardedRef} {...rest} />
{children}

</div>);
上面的组件会将 forwardedRef 分配给 ref 属性, 当渲染子组件的时候,input 输入框就会接收到这个 ref 。…rest 是 props 的解构(也就是说,我们会将 rest 数组中的所有参数作为 props 传递给 input 组件)。那么我们该如何使用 TextInput 组件呢?像这样:
[JavaScript] 纯文本查看 复制代码
?

const InputField = Input(TextInput);class CustomTextInput extends Component {
render() {

const inputRef = React.createRef();

return <InputField ref={inputRef} />;

}}
最后,将 TextInput 传入 Input 高阶组件,会返回一个 InputField component。
创建一个 ref ,并作为参数传递给 InputField 组件。
结论
与通过 props 和 state 不同,Refs 是一种将数据传递给特定子实例的好方法。
你必须要小心,因为 refs 操纵实际的 DOM,而不是虚拟的 DOM,这与 React 思维方式相矛盾。因此,虽然 refs 不应该是通过应用程序流动数据的默认方法,但是当您需要时,它们是可以从 DOM 元素读取数据的好方法。

点赞
收藏
评论区
推荐文章
晴空闲云 晴空闲云
3年前
vue3中基于script setup语法糖的$refs使用
在用vue3开发项目的时候,需要调用子组件的方法,于是想着用$refs来实现,但是我是使用scriptsetup语法糖,原先vue2的语法已经不适用了。于是一番折腾和查阅资料,终于搞定。vue2语法vue2语法在组件上设置ref属性后,在代码里可以通过this.$refs.ref值访问到对应的子组件。一个设置ref值的组件:html在js代码中可以通
爱丽丝13 爱丽丝13
4年前
React 组件通信之发布订阅模式
React通信react的数据流是单向的,react通信有以下几种方式:父向子通信:传入props子向父通信:父组件向子组件传一个函数,然后通过这个函数的回调,拿到子组件传过来的值父向孙通信:利用context传值。React.createContext()兄弟间通信:​1、找一个相同的父组件,既可以用pr
亚瑟 亚瑟
4年前
自定义 Hook – React
自定义Hook_Hook_是React16.8的新增特性。它可以让你在不编写class的情况下使用state以及其他的React特性。通过自定义Hook,可以将组件逻辑提取到可重用的函数中。在我们学习时,我们已经见过这个聊天程序中的组件,该组件用于显示好友的在线状态:importReact,{useSta
Stella981 Stella981
3年前
React 服务端渲染完美的解决方案
最近在开发一个服务端渲染工具,通过一篇小文大致介绍下服务端渲染,和服务端渲染的方式方法。在此文后面有两中服务端渲染方式的构思,根据你对服务端渲染的利弊权衡,你会选择哪一种服务端渲染方式呢?什么是服务器端渲染使用React构建客户端应用程序,默认情况下,可以在浏览器中输出React组件,进行生成DOM和操作DOM。Re
Wesley13 Wesley13
3年前
03. react 初次见面
    与浏览器的DOM元素不同,React当中的元素事实上是普通的对象,ReactDOM可以确保浏览器DOM的数据内容与React元素保持一致。1、将元素渲染到DOM中    首先我们在一个HTML页面中添加一个id"root" 的<div:<divid"root"
Stella981 Stella981
3年前
Flutter子组件调用父组件方法修改父组件参数
子组件调用父级组件方法的主要实现是父组件给子组件传入一个方法,然后在子组件中调用父级方法来修改父级的参数。看一下效果图!(https://img2018.cnblogs.com/blog/1038765/201907/103876520190702152130487420290106.gif)父级组件实现在父级组件中写一个
Stella981 Stella981
3年前
React的单向数据流与组件间的沟通
今天来给大家总结下React的单向数据流与组件间的沟通。首先,我认为使用React的最大好处在于:功能组件化,遵守前端可维护的原则。先介绍单向数据流吧。React单向数据流:React是单向数据流,数据主要从父节点传递到子节点(通过props)。如果顶层(父级)的某个props改变了,React会重渲染所有的子节点。刚才我们提到了
Stella981 Stella981
3年前
React Portals
Portal提供了一种将子节点渲染到存在于父组件以外的DOM节点的优秀的方案。常见场景:对话框、悬浮卡以及提示框!定义一个模态框组件:importReact,{useRef,useEffect,}from'react';importReactDOMfrom'reactdo
Stella981 Stella981
3年前
React.js 时间组件 + 组件生命周期(更新模拟)
React是用于构建用户界面的JavaScript库,React组件使用一个名为render()的方法,接收数据作为输入,输出页面中对应展示的内容。React除了可以使用外部传入的数据以外(通过this.props访问传入数据),组件还可以拥有其内部的状态数据(通过this.state访问状态数据)。当组件的状态
Stella981 Stella981
3年前
React 第一个小游戏(井字棋)知识关键点
1、React是一个声明式,高效且灵活的用于构建用户界面的JavaScript库通过使用组件来告诉React我们希望在屏幕上看到什么。当数据发生变化时,React会高效的更新并重新渲染我们的组件2、render返回了一个React元素,这是一种对渲染内容的轻量级描述。大多数的React开发者使用了一种名为"JSX"的特
React memo的原理、实践与思考
前言在react中,对一个组件进行点击事件等操作时,该组件以及该组件的子组件都会重新渲染。避免组件的重新渲染一般可以借助React.memo、useCallback等来实现。什么是memomemo原理memo类似于class中pureComponent的特