英文:Yomi Eluwande  译文:joking_zhang

https://segmentfault.com/a/1190000019277029

使用 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 <div ref={this.firstRef} />;  }}

如上所示:

  • 一个 ref 实例在构造函数中创建,并赋值给 this.firstRef

  • 在 render() 方法内部,将构造函数中创建的 ref 传递给 div

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

使用 Refs 聚焦输入

这是另一个例子:

// 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 元素。

<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.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 的值。

<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.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 标签:

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

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

转发 Refs (Forwarding Refs)

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

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

// 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 :

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 函数会返回 InputComponent。forwardRef 函数中所包含的 ref 参数,是由 React.forwardRef 函数创建的。 高阶组件最终会将包装好的组件作为值返回。

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

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

上面的组件会将 forwardedRef 分配给 ref 属性, 当渲染子组件的时候,input 输入框就会接收到这个 ref 。…rest 是 props 的解构(也就是说,我们会将 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 组件。

结论

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

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

每一个“在看”,都是对我最大的肯定!

【React】282- 在 React 组件中使用 Refs 指南的更多相关文章

  1. 在 React 组件中使用 Refs 指南

    原文:Fullstack React's Guide to using Refs in React Components作者:Yomi Eluwande译者:博轩 译文:https://segment ...

  2. 九、React中的组件、父子组件、React props父组件给子组件传值、子组件给父组件传值、父组件中通过refs获取子组件属性和方法

    一.概述 React中的组件: 解决html 标签构建应用的不足. 使用组件的好处:把公共的功能单独抽离成一个文件作为一个组件,哪里里使用哪里引入. [父子组件]:组件的相互调用中,我们把调用者称为父 ...

  3. react中的refs

    概述 很久之前就知道refs,感觉好神秘,恰好今天突然发现字符串形式的ref在官网不推荐使用了,于是好好总结一下ref的用法,供以后开发时参考,相信对其他人也有用. 参考资料: Refs & ...

  4. react 不能往组件中传入属性的值为 undefined

    在使用 andt design 的时候遇到个需求,需要清除 Select 组件选中后的值,让它变成什么都没选中,显示 placeholder 刚开始以为设置为 null 即可,结果发现设置为 null ...

  5. 规避 React 组件中的 bind(this)

    React 组件中处理 onClick 类似事件绑定的时候,是需要显式给处理器绑定上下文(context)的,这一度使代码变得冗余和难看. 请看如下的示例: class App extends Com ...

  6. react组件中的constructor和super小知识

    react组件中的constructor和super小知识 1.react中用class申明的类一些小知识 如上图:类Child是通过class关键字申明,并且继承于类React. A.Child的类 ...

  7. React组件中的key

    React组件中的key 一.key的作用 react中的key属性,它是一个特殊的属性,它是出现不是给开发者用的(例如你为一个组件设置key之后不能获取组件的这个key props),而是给reac ...

  8. react+dva 全局model中异步获取数据state在组件中取不到值

    先上结论,不是取不到,是写法有问题. 全文分4部分,1是问题描述,2是一开始的解决想法(错误做法),3是问题产生原因的思考,4是正常解决方法.只想看结论直接跳4 1.问题描述 接触react dva一 ...

  9. React组件中对子组件children进行加强

    React组件中对子组件children进行加强 问题 如何对组件的children进行加强,如:添加属性.绑定事件,而不是使用<div>{this.props.children}< ...

随机推荐

  1. lqb 基础练习 数列特征

    基础练习 数列特征 时间限制:1.0s   内存限制:256.0MB     问题描述 给出n个数,找出这n个数的最大值,最小值,和. 输入格式 第一行为整数n,表示数的个数. 第二行有n个数,为给定 ...

  2. Mybatis 关联对象不能输出的解决办法

    Mybatis 关联对象不能输出的解决办法 1.如图所示,现在进行查询的时候并没有得到来自另一张表address项 2.我们进行如下配置: (1).在mybatis-config.xml 文件中配置, ...

  3. 管道 |、|&、tee

    用“|”或“|&”隔开两个命令之间形成一个管道,左边命令的标准输出(|)或者标准错误输出(|&)信息流入到右边命令的标准输入,即左边命令的标准输出作为右边命令的标准输入.如: make ...

  4. JavaScript 关于setTimeout与setInterval的小研究

    说明 在开发功能"轨迹播放"时,遇到了一个情况. 原先同事已经开发了一版,这次有个新功能:点击线上任意一点后可以从点击处重新播放. 看了一下原来的版本,发现同时使用了setTime ...

  5. react -Route exact Redirect

     exact是Route下的一个属性,react路由会匹配到所有能匹配到的路由组件,exact能够使得路由的匹配更严格一些(exact的值为bool型).   <Route path='/' c ...

  6. 数据库求闭包,求最小函数依赖集,求候选码,判断模式分解是否为无损连接,3NF,BCNF

    1.说白话一点:闭包就是由一个属性直接或间接推导出的所有属性的集合. 例(1):   设有关系模式R(U,F),其中U={A,B,C,D,E,I},F={A→D,AB→E,BI→E,CD→I,E→C} ...

  7. linux进程管理常用命令

    初始化进程在centos5,6,7中的发展: 在centos5中使用sysv init 是一个shell脚本,依靠依次执行脚本中的命令启动系统,只能串行执行. 在centos6中使用upstart,也 ...

  8. linux磁盘分区三步走

    为了便于理解硬盘的物理结构 ,可将硬盘看作一个圆,它是坚硬金属材料制成的涂以磁性介质的盘片,不同容量硬盘的盘片数不等.每个盘有两面,都可记录信息.要了解硬盘的物理结构,需要弄懂磁道.扇区.柱面.簇等几 ...

  9. Python开发-实现Excel套打打印

    一.目的 目前本人就职与甲方的工作,由于公司的ERP比较烂无法完美的设计套打,就想着自己用Python开发一个套打工具. 二.开发过程 刚开始我打算用Html的方式生成打印的文档,但是有两个无法解决的 ...

  10. 修改Linux克隆的物理地址 和 IP地址

    实在不行就重新启动一下   才会改成 ech0 完成后文件配置环境变量 作为一个真正的程序员,首先应该尊重编程,热爱你所写下的程序,他是你的伙伴,而不是工具.