第八章 react组件实例中三大属性之ref

简介: 第八章 react组件实例中三大属性之ref

接下来我们要学习最后一个属性ref了,官网是这样描述的:

React 支持一个特殊的、可以附加到任何组件上的 ref 属性。此属性可以是一个由 React.createRef() 函数创建的对象、或者一个回调函数、或者一个字符串(遗留 API)。当 ref 属性是一个回调函数时,此函数会(根据元素的类型)接收底层 DOM 元素或 class 实例作为其参数。这能够让你直接访问 DOM 元素或组件实例。

谨慎使用 ref。如果你发现自己经常使用 ref 来在应用中“实现想要的功能”,你可以考虑去了解一下自上而下的数据流。

案例分析

   我们写一个获取输入框内容的案例,来说明ref的使用。

<!-- 准备好员工“容器” -->
  <div id="app"></div>
  <!-- 引入ReactJS核心库 -->
  <script type="text/javascript" src="../JS/react.development.js"></script>
  <!-- 引入React-DOM核心库,用于操作DOM -->
  <script type="text/javascript" src="../JS/react-dom.development.js"></script>
  <!-- 引入Babel,用于编译jsx为js -->
  <script type="text/javascript" src="../JS/babel.min.js"></script>
  <!-- 此处类型为babel -->
  <script type="text/babel">
    class Demo extends React.Component {
      render () {
        return (
          <div>
            <input type="text" placeholder="点击按钮获取值" />&nbsp;
            <button >click获取值</button>&nbsp;
            <input type="text" placeholder="失去焦点获取值" />  
          </div>
        )
      }
    }
    // 2、将虚拟DOM渲染到页面,标签必须闭合
    ReactDOM.render(<Demo />,document.getElementById('app'))
 </script>

我们要对以上组件实现2个功能:

  • 点击按钮获取第一个输入框的值
  • 第二个输入框失去焦点时获取其值
传统方法:通过属性id获取元素节点,拿到其值
class Demo extends React.Component {
    // 获取第一个输入框的值
  getInput1 = () => {
        const input1 = document.getElementById('input1')
        console.log(input1.value)
    }
    // 获取第二个输入框的值
    getInput2 = () => {
        const input2 = document.getElementById('input2')
        console.log(input2.value)
    }
      render () {
        return (
          <div>
            <input id="input1" type="text" placeholder="点击按钮获取值" />&nbsp;
            <button onClick={this.getInput1}>click获取值</button>&nbsp;
            <input id="input2" onBlur={this.getInput2} type="text" placeholder="失去焦点获取值" />  
          </div>
        )
      }
    }
// 2、将虚拟DOM渲染到页面,标签必须闭合
ReactDOM.render(<Demo />,document.getElementById('app'))

以上代码,我们是通过传统的获取元素id来拿到值,功能可以实现,但是在react中我们就要使用react的方法。

使用字符串的ref获取输入框的值:
class Demo extends React.Component {
    // 获取第一个输入框的值
  getInput1 = () => {
        const input1 = this.refs.input1
        console.log(input1.value)
    }
    // 获取第二个输入框的值
    getInput2 = () => {
        const input2 = this.refs.input2
        console.log(input2.value)
    }
      render () {
        return (
          <div>
            <input ref="input1" type="text" placeholder="点击按钮获取值" />&nbsp;
            <button onClick={this.getInput1}>click获取值</button>&nbsp;
            <input ref="input2" onBlur={this.getInput2} type="text" placeholder="失去焦点获取值" />  
          </div>
        )
      }
    }
// 2、将虚拟DOM渲染到页面,标签必须闭合
ReactDOM.render(<Demo />,document.getElementById('app'))

以上代码,我们是通过ref标识元素节点来获取输入框的值,功能可以实现,且代码比传统方法简洁。

注意:在React中,字符串形式的ref已经被废弃,不再推荐使用。字符串形式的ref是一种在React早期版本中使用的方式

使用回调函数的Ref
class Demo extends React.Component {
    // 获取第一个输入框的值
  getInput1 = () => {
        const {input1} = this
        console.log(input1.value)
    }
    // 获取第二个输入框的值
    getInput2 = () => {
        const {input2} = this
        console.log(input2.value)
    }
      render () {
        return (
          <div>
            <input ref={c => this.input1 = c} type="text" placeholder="点击按钮获取值" />&nbsp;
            <button onClick={this.getInput1}>click获取值</button>&nbsp;
            <input ref={c => this.input2 = c} onBlur={this.getInput2} type="text" placeholder="失去焦点获取值" />  
          </div>
        )
      }
    }
// 2、将虚拟DOM渲染到页面,标签必须闭合
ReactDOM.render(<Demo />,document.getElementById('app'))

以上代码,我们使用的是箭头函数作为回调函数赋值给ref,箭头函数本身没有this,这里的this其实是组件实例本身,我们这里回调函数获取到的参数其实就是ref标识的这个节点元素,我们将当前节点赋值给这个组件实例。功能同样可以实现。

扩展内联回调Ref和class绑定的回调ref

关于回调 refs 的说明

如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。

以上是react官方对内联函数的说明。

我们以代码来重现问题:

class Demo extends React.Component {
    // 获取第一个输入框的值
  getInput1 = () => {
        const {input1} = this
        console.log(input1.value)
    }
    // 初始化状态
    state = {isHot:true}
  // 切换天气
  changeWeather = () => {
        const {isHot} = this.state
        this.setState({isHot:!isHot})
    }
      render () {
          const {isHot} = this.state
        return (
          <div>
            <h1>今天天气很{isHot?'炎热':'凉爽'}</h1>
            <input ref={(c)=>{this.input1 = c; console.log('@',c);}} type="text" />
            <button onClick={this.getInput1}>click获取值</button> 
      <button onClick={this.changeWeather}>修改状态</button>
          </div>
        )
      }
    }
// 2、将虚拟DOM渲染到页面,标签必须闭合
ReactDOM.render(<Demo />,document.getElementById('app'))

以上代码,我们在内联函数中加了一个打印日志语句,初始化组件时我们的内联函数会执行一次,但是当我们切换天气更新组件状态时,内联函数会执行两次:

@ null
@  <input type="text" />

这是为什么呢?

这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。

根据官方的解释,是因为我们更新组件状态时,react发现我们这个是一个内联函数,调用第一次的时候清空旧的实例,所以我们第一次打印的是null,清空以后设置了一个新的实例,所以我们第二次拿到的就是新的实例。

使用class绑定的回调函数给ref

class Demo extends React.Component {
    // 获取第一个输入框的值
  getInput1 = () => {
        const {input1} = this
        console.log(input1.value)
    }
    // 初始化状态
    state = {isHot:true}
  // 切换天气
  changeWeather = () => {
        const {isHot} = this.state
        this.setState({isHot:!isHot})
    }
    // 绑定ref的回调函数
    inputRef = (c) => {
        this.input1 = c;
        console.log('@',c)
    }
      render () {
          const {isHot} = this.state
        return (
          <div>
            <h1>今天天气很{isHot?'炎热':'凉爽'}</h1>
{/*<input ref={(c)=>{this.input1 = c; console.log('@',c);}} type="text" />*/}
      <input ref={this.inputRef} type="text"/>
            <button onClick={this.getInput1}>click获取值</button> 
      <button onClick={this.changeWeather}>修改状态</button>
          </div>
        )
      }
    }
// 2、将虚拟DOM渲染到页面,标签必须闭合
ReactDOM.render(<Demo />,document.getElementById('app'))

以上代码,我们使用class绑定的函数给ref进行回调,当我们进行状态更新的时候,其打印语句只执行一次。

react推荐的createRef

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

createRef 是 React 16.3 引入的一个 API,用于创建一个 ref 对象,可以将其赋值给组件的 ref 属性,从而可以在组件外部访问组件内部的 DOM 节点或组件实例。

class Demo extends React.Component {
      myRef1 = React.createRef()
      myRef2 = React.createRef()
      getData = () => {
        const val1 = this.myRef1.current.value
        console.log(val1)
      }
      blurData = () => {
        const val2 = this.myRef2.current.value
        console.log(val2)
      }
      render () {
        return (
          <div>
            <input ref={this.myRef1} type="text"/>&nbsp;
            <button onClick={this.getData}>click</button>&nbsp;
            <input ref={this.myRef2} onBlur={this.blurData} type="text"/>  
          </div>
        )
      }
    }
    // 2、将虚拟DOM渲染到页面,标签必须闭合
    ReactDOM.render(<Demo />,document.getElementById('app'))

以上代码,ref赋值的是由createRef创建出来的ref对象。用于接收底层 DOM 元素作为其 current 属性。


总结

在React中,ref 是一个特殊的属性,用于引用组件内部的 DOM 节点或组件实例。ref 属性可以是一个字符串,也可以是一个回调函数,还可以是一个 React.createRef() 创建的 ref 对象。

使用字符串作为 ref 属性的值已经被废弃,不推荐使用。推荐的做法是使用回调函数或 React.createRef()。

注意:在函数组件中不能使用ref属性,因为它们没有实例。


相关文章
|
3天前
|
前端开发 JavaScript 开发者
React 按钮组件 Button
本文介绍了 React 中按钮组件的基础概念,包括基本的 `&lt;button&gt;` 元素和自定义组件。详细探讨了事件处理、参数传递、状态管理、样式设置和可访问性优化等常见问题及其解决方案,并提供了代码示例。帮助开发者避免易错点,提升按钮组件的使用体验。
103 77
|
4天前
|
前端开发 UED 开发者
React 对话框组件 Dialog
本文详细介绍了如何在 React 中实现一个功能完备的对话框组件(Dialog),包括基本用法、常见问题及其解决方案,并通过代码案例进行说明。从安装依赖到创建组件、添加样式,再到解决关闭按钮失效、背景点击无效、键盘导航等问题,最后还介绍了如何添加动画效果和处理异步关闭操作。希望本文能帮助你在实际开发中更高效地使用 React 对话框组件。
98 75
|
9天前
|
前端开发 Java API
React 进度条组件 ProgressBar 详解
本文介绍了如何在 React 中创建进度条组件,从基础实现到常见问题及解决方案,包括动态更新、状态管理、性能优化、高级动画效果和响应式设计等方面,帮助开发者构建高效且用户体验良好的进度条。
37 18
|
18小时前
|
存储 前端开发 UED
React 面包屑组件 Breadcrumb 详解
面包屑导航是现代Web应用中常见的UI元素,帮助用户了解当前位置并快速返回上级页面。本文介绍如何使用React构建面包屑组件,涵盖基本概念、实现方法及常见问题。通过函数式组件和钩子,结合React Router动态生成路径,处理嵌套路由,并确保可访问性。示例代码展示了静态和动态面包屑的实现,帮助开发者提升用户体验。
43 30
|
17天前
|
存储 前端开发 JavaScript
React 表单输入组件 Input:常见问题、易错点及解决方案
本文介绍了在 React 中使用表单输入组件 `Input` 的基础概念,包括受控组件与非受控组件的区别及其优势。通过具体代码案例,详细探讨了创建受控组件、处理多个输入字段、输入验证和格式化的方法,并指出了常见易错点及避免方法,旨在提升表单的健壮性和用户体验。
28 4
|
1月前
|
前端开发 JavaScript 开发者
颠覆传统:React框架如何引领前端开发的革命性变革
【10月更文挑战第32天】本文以问答形式探讨了React框架的特性和应用。React是一款由Facebook推出的JavaScript库,以其虚拟DOM机制和组件化设计,成为构建高性能单页面应用的理想选择。文章介绍了如何开始一个React项目、组件化思想的体现、性能优化方法、表单处理及路由实现等内容,帮助开发者更好地理解和使用React。
77 9
|
2月前
|
前端开发
深入解析React Hooks:构建高效且可维护的前端应用
本文将带你走进React Hooks的世界,探索这一革新特性如何改变我们构建React组件的方式。通过分析Hooks的核心概念、使用方法和最佳实践,文章旨在帮助你充分利用Hooks来提高开发效率,编写更简洁、更可维护的前端代码。我们将通过实际代码示例,深入了解useState、useEffect等常用Hooks的内部工作原理,并探讨如何自定义Hooks以复用逻辑。
|
2月前
|
前端开发 JavaScript API
探索React Hooks:前端开发的革命性工具
【10月更文挑战第5天】探索React Hooks:前端开发的革命性工具
|
26天前
|
监控 前端开发 数据可视化
3D架构图软件 iCraft Editor 正式发布 @icraft/player-react 前端组件, 轻松嵌入3D架构图到您的项目,实现数字孪生
@icraft/player-react 是 iCraft Editor 推出的 React 组件库,旨在简化3D数字孪生场景的前端集成。它支持零配置快速接入、自定义插件、丰富的事件和方法、动画控制及实时数据接入,帮助开发者轻松实现3D场景与React项目的无缝融合。
100 8
3D架构图软件 iCraft Editor 正式发布 @icraft/player-react 前端组件, 轻松嵌入3D架构图到您的项目,实现数字孪生
|
1月前
|
前端开发 JavaScript 开发者
使用React和Redux构建高效的前端应用
使用React和Redux构建高效的前端应用
33 1