第八章 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 分配给实例属性,以便可以在整个组件中引用它们。

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

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天界程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值