当前位置:   article > 正文

React进阶 - 15(React 中 ref 的使用)

React进阶 - 15(React 中 ref 的使用)

本章内容

上一节我们了解了 React中的”虚拟DOM“中的”Diff算法““ ,本节我们来说一说 Reactref的使用

一、e.target 获取事件对应“元素”的DOM节点

  • 打开之前工程中的 TodoList.js代码
  • 需求:当 input框中的数据变化时,要求拿到这个input框节点 DOM
  • 这个需求我们可以使用最原始的 e.target来实现
import React, { Component, Fragment } from "react";
import TodoItem from "./TodoItem";

class TodoList extends Component{
  constructor(props) {
    super(props)
    this.deleteData = this.deleteData.bind(this)
    this.addListData = this.addListData.bind(this)
    this.changeInputValue = this.changeInputValue.bind(this)
    this.state = {
      inputValue: '', 
      list: []
    } 
  }

  render() {  
    console.log('render 函数执行了')
    return (
      <Fragment>
        <div>
          <input value={this.state.inputValue} onChange={this.changeInputValue} />
          <button onClick={this.addListData}> 提交 </button>
        </div>

        <ul> {this.getTodoItem()} </ul>
      </Fragment>
    )
  }

  getTodoItem() {
    return this.state.list.map((item, index) => {
      return <TodoItem key={index} content={item} index={index} deleteFn={this.deleteData}></TodoItem>
    })
  }

  deleteData(index) {
    this.setState((prevState) => {
      const list = [...prevState.list]
      list.splice(index, 1)
      return {list}
    })

  }

  addListData() {
    this.setState((prevState) => ({
      list: [...prevState.list, prevState.inputValue],
      inputValue: ''
    }))
  }

  // 1、当input框里的值发生变化后,该函数会执行
  changeInputValue(e) {
  // 2、我们试着在控制台打印出 e.target,观察其内容
   console.log(e.target)
  // 3、拿到输入框的数据
    const value = e.target.value
  // 4、更新inputValue 数据
    this.setState(() => ({inputValue: value})) 
  }
}

export default TodoList
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 运行代码,打开浏览器的控制台,可以看到,每当输入框的数据变化,就会打印出 input框的 DOM节点
    在这里插入图片描述

  • 从上面的演示可以看出,在 React中,仍然可以使用 e.target获取事件对应“元素”的DOM节点

二、ref

  • refreference的简写,其含义为“引用”
  • React中,我们可以使用 ref来获取 DOM元素。但是一般情况下,我们尽量不要去使用 ref,但有时一些复杂的业务(如:动画),其不可避免的还是会用到页面上的一些 DOM标签。
  • 我们使用 ref来实现上面的需求:“当 input框中的数据变化时,要求拿到这个input框节点 DOM
import React, { Component, Fragment } from "react";
import TodoItem from "./TodoItem";

class TodoList extends Component{
  constructor(props) {
    super(props)
    this.deleteData = this.deleteData.bind(this)
    this.addListData = this.addListData.bind(this)
    this.changeInputValue = this.changeInputValue.bind(this)
    this.state = {
      inputValue: '', 
      list: []
    } 
  }

  render() {  
    return (
      <Fragment>
        <div>
          {/* 
           1、首先给 input 元素添加一个 ref 属性,它等于一个”箭头函数”。
           ”箭头函数“自动收到一个参数,名字可以任意取

           2、箭头函数使用一个固定的写法 this.input=input,表示,
           我构建了一个“引用”ref,这个“引用”叫做 this.input,
           它指向 input框对应的 DOM 节点

           3、接着我们使用 ref 方式获得的 DOM 节点来替换 changeInputValue 方法里的 e.target.value 写法

          */}
          <input value={this.state.inputValue} onChange={this.changeInputValue} ref={(input) => this.input = input} />
          <button onClick={this.addListData}> 提交 </button>
        </div>

        <ul> {this.getTodoItem()} </ul>
      </Fragment>
    )
  }

  getTodoItem() {
    return this.state.list.map((item, index) => {
      return <TodoItem key={index} content={item} index={index} deleteFn={this.deleteData}></TodoItem>
    })
  }

  deleteData(index) {
    this.setState((prevState) => {
      const list = [...prevState.list]
      list.splice(index, 1)
      return {list}
    })

  }

  addListData() {
    this.setState((prevState) => ({
      list: [...prevState.list, prevState.inputValue],
      inputValue: ''
    }))
  }

  // 4、去掉入参 e, 使用 ref 方式获得的 DOM 节点来替换 e.target.value 写法
  changeInputValue() {
    console.log(this.input)
    const value = this.input.value
    this.setState(() => ({inputValue: value})) 
  }
}

export default TodoList
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 运行代码,打开浏览器的控制台,可以看到效果正常运行,无报错。所以说 ref用于获取 DOM节点是可以被正常使用的。但,注意不要滥用,React是“数据驱动”的框架,非特殊情况,不要主动去操作 DOM,而且,用 ref时,一不小心就会有bug,比如和 setState合用
    在这里插入图片描述

三、ref 和 setState 合用

  • 上面我们提到 refsetState合用时,有的时候会有bug。现在我们用一个例子来说明

  • 需求是这样的:当输入框输入内容并点击“提交”,要求打印出 list数据的 DOM节点内容以及长度(即:打印 ul元素节点,以及ul中的div长度)

  • 要实现上面的需求,思路大概是

1、给 ul 绑定 ref 属性,获取到 DOM 节点

2、在“提交”按钮点击事件 “addListData” 中打印 ul 节点,并使用原生的 querySelectorAll 方法 打印 ul 中 div 的长度

  • 1
  • 2
  • 3
  • 4
import React, { Component, Fragment } from "react";
import TodoItem from "./TodoItem";

class TodoList extends Component{
  constructor(props) {
    super(props)
    this.deleteData = this.deleteData.bind(this)
    this.addListData = this.addListData.bind(this)
    this.changeInputValue = this.changeInputValue.bind(this)
    this.state = {
      inputValue: '', 
      list: []
    } 
  }

  render() {  
    return (
      <Fragment>
        <div>
          <input value={this.state.inputValue} onChange={this.changeInputValue} ref={(input) => this.input = input} />
          <button onClick={this.addListData}> 提交 </button>
        </div>

        {/* 1、给 ul 绑定 ref 属性,获取到 DOM 节点  */}
        <ul ref={(ul) => this.ul = ul }> {this.getTodoItem()} </ul>
      </Fragment>
    )
  }

  getTodoItem() {
    return this.state.list.map((item, index) => {
      return <TodoItem key={index} content={item} index={index} deleteFn={this.deleteData}></TodoItem>
    })
  }

  deleteData(index) {
    this.setState((prevState) => {
      const list = [...prevState.list]
      list.splice(index, 1)
      return {list}
    })

  }

  // 2、在“提交”按钮点击事件 “addListData” 中打印 ul 节点
  // 并使用原生的 querySelectorAll 方法 打印 ul 中 div 的长度
  addListData() {
    console.log(this.ul) // 打印 ul 节点
    console.log(this.ul.querySelectorAll("div").length) // 使用原生的 querySelectorAll 方法 打印 ul 中 div 的长度

    this.setState((prevState) => ({
      list: [...prevState.list, prevState.inputValue],
      inputValue: ''
    }))
  }

  changeInputValue() {
    const value = this.input.value
    this.setState(() => ({inputValue: value})) 
  }
}

export default TodoList
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 运行代码,回到页面控制台查看 this.ul.querySelectorAll("div").length打印出的 div长度,发现输出的长度跟真实的长度不对等,输出长度比实际长度少1个!!!
    在这里插入图片描述

  • 为什么会出现这个问题呢?

答:React 中 setState 被设计成异步的,当数据变化时, setState 中的函数体并不会立即执行,它要等一会儿。

而 console.log(this.ul.querySelectorAll("div").length) 从页面效果来看是在 setState 底层运行之前执行了
  • 1
  • 2
  • 3
  • 怎么解决这个问题呢?很简单,setState里面有第二个参数(也是一个函数),我们可以在这个函数里执行 console.log(this.ul.querySelectorAll("div").length)
  // 2、在“提交”按钮点击事件 “addListData” 中打印 ul 节点
  // 并使用原生的 querySelectorAll 方法 打印 ul 中 div 的长度
  addListData() {
    this.setState((prevState) => ({
      list: [...prevState.list, prevState.inputValue],
      inputValue: ''
    }), () => {
      console.log(this.ul) // 打印 ul 节点
      console.log(this.ul.querySelectorAll("div").length) // 使用原生的 querySelectorAll 方法 打印 ul 中 div 的长度
    })
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 再次运行代码,发现这个问题就解决了~
    在这里插入图片描述

到此,本章内容结束!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/65823
推荐阅读
相关标签
  

闽ICP备14008679号