第八章 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属性,因为它们没有实例。


相关文章
|
4月前
|
缓存 前端开发 JavaScript
React Hooks深度解析与最佳实践:提升函数组件能力的终极指南
🌟蒋星熠Jaxonic,前端探索者。专注React Hooks深度实践,从原理到实战,分享状态管理、性能优化与自定义Hook精髓。助力开发者掌握函数组件的无限可能,共赴技术星辰大海!
React Hooks深度解析与最佳实践:提升函数组件能力的终极指南
|
9月前
|
缓存 前端开发 数据安全/隐私保护
如何使用组合组件和高阶组件实现复杂的 React 应用程序?
如何使用组合组件和高阶组件实现复杂的 React 应用程序?
334 68
|
9月前
|
缓存 前端开发 Java
在 React 中,组合组件和高阶组件在性能方面有何区别?
在 React 中,组合组件和高阶组件在性能方面有何区别?
300 67
|
9月前
|
前端开发 JavaScript 安全
除了高阶组件和render props,还有哪些在 React 中实现代码复用的方法?
除了高阶组件和render props,还有哪些在 React 中实现代码复用的方法?
372 62
|
11月前
|
编解码 前端开发 开发者
React 图片组件样式自定义:常见问题与解决方案
在 React 开发中,图片组件的样式自定义常因细节问题导致布局错乱、性能损耗或交互异常。本文系统梳理常见问题及解决方案,涵盖基础样式应用、响应式设计、加载状态与性能优化等,结合代码案例帮助开发者高效实现图片组件的样式控制。重点解决图片尺寸不匹配、边框阴影不一致、移动端显示模糊、加载失败处理及懒加载等问题,并总结易错点和最佳实践,助力开发者提升开发效率和用户体验。
376 22
|
JavaScript 前端开发
学习React中ref的两个demo
为了摆脱繁琐的Dom操作, React提倡组件化, 组件内部用数据来驱动视图的方式,来实现各种复杂的业务逻辑 ,然而,当我们为原始Dom绑定事件的时候, 还需要通过组件获取原始的Dom, 而React也提供了ref为我们解决这个问题. 为什么不能从组件直接获取Dom? 组件并不是真实的 DOM 节点,而是存在于内存之中的一种数据结构,叫做虚拟 DOM (virtual DOM)。
1337 0
|
设计模式 前端开发 数据可视化
【第4期】一文了解React UI 组件库
【第4期】一文了解React UI 组件库
882 0
|
存储 前端开发 JavaScript
【第34期】一文学会React组件传值
【第34期】一文学会React组件传值
254 0
|
前端开发
【第31期】一文学会用React Hooks组件编写组件
【第31期】一文学会用React Hooks组件编写组件
239 0
|
资源调度 前端开发 JavaScript
React 的antd-mobile 组件库,嵌套路由
React 的antd-mobile 组件库,嵌套路由
497 0