本章内容
目录
- [一、e.target 获取事件对应"元素"的DOM节点](#一、e.target 获取事件对应“元素”的DOM节点)
- 二、ref
- [三、ref 和 setState 合用](#三、ref 和 setState 合用)
上一节我们了解了 React
中的"虚拟DOM"中的"Diff算法"" ,本节我们来说一说 React
中 ref
的使用
一、e.target 获取事件对应"元素"的DOM节点
- 打开之前工程中的
TodoList.js
代码 - 需求:当
input
框中的数据变化时,要求拿到这个input
框节点DOM
- 这个需求我们可以使用最原始的
e.target
来实现
js
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
-
运行代码,打开浏览器的控制台,可以看到,每当输入框的数据变化,就会打印出
input
框的DOM
节点
-
从上面的演示可以看出,在
React
中,仍然可以使用e.target
获取事件对应"元素"的DOM
节点
二、ref
ref
是reference
的简写,其含义为"引用"- 在
React
中,我们可以使用ref
来获取DOM
元素。但是一般情况下,我们尽量不要去使用 ref,但有时一些复杂的业务(如:动画),其不可避免的还是会用到页面上的一些DOM
标签。 - 我们使用
ref
来实现上面的需求:"当input
框中的数据变化时,要求拿到这个input
框节点DOM
"
js
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
- 运行代码,打开浏览器的控制台,可以看到效果正常运行,无报错。所以说
ref
用于获取DOM
节点是可以被正常使用的。但,注意不要滥用,React
是"数据驱动"的框架,非特殊情况,不要主动去操作DOM
,而且,用ref
时,一不小心就会有bug
,比如和setState
合用
三、ref 和 setState 合用
-
上面我们提到
ref
和setState
合用时,有的时候会有bug。现在我们用一个例子来说明 -
需求是这样的:当输入框输入内容并点击"提交",要求打印出
list
数据的DOM
节点内容以及长度(即:打印ul
元素节点,以及ul
中的div
长度) -
要实现上面的需求,思路大概是
text
1、给 ul 绑定 ref 属性,获取到 DOM 节点
2、在"提交"按钮点击事件 "addListData" 中打印 ul 节点,并使用原生的 querySelectorAll 方法 打印 ul 中 div 的长度
js
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
-
运行代码,回到页面控制台查看
this.ul.querySelectorAll("div").length
打印出的div
长度,发现输出的长度跟真实的长度不对等,输出长度比实际长度少1个!!!
-
为什么会出现这个问题呢?
text
答:React 中 setState 被设计成异步的,当数据变化时, setState 中的函数体并不会立即执行,它要等一会儿。
而 console.log(this.ul.querySelectorAll("div").length) 从页面效果来看是在 setState 底层运行之前执行了
- 怎么解决这个问题呢?很简单,
setState
里面有第二个参数(也是一个函数),我们可以在这个函数里执行console.log(this.ul.querySelectorAll("div").length)
js
// 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 的长度
})
}
- 再次运行代码,发现这个问题就解决了~
到此,本章内容结束!