(15)React 进阶——⑤ React 中 ref 的使用 | React 基础理论实操ref 是 referenc
转载请注明出处,未经同意,不可修改文章内容。
🔥🔥🔥“前端一万小时”两大明星专栏——“从零基础到轻松就业”、“前端面试刷题”,已于本月大改版,合二为一,干货满满,欢迎点击公众号菜单栏各模块了解。
涉及面试题:
1. refs 有什么用?
2. 如何创建 refs?
3. 什么是 forward refs?
4. callback refs 和 findDOMNode() 哪一个是首选选项?
5. 为什么 String Refs 被弃用?
6. 什么是受控组件?
7. 什么是非受控组件?
8. 如何在页面加载时聚焦一个输入元素?
9. 在 React 中如何以编程方式触发点击事件?
10. 你什么时候需要使用 refs?
编号:[react_15]
1 e.target
ref 是 reference 的简写,其含义为“引用”。
❗️在 React 中,我们可以使用 ref 来获取 DOM 元素。一般情况下,我们尽量不要去使用 ref,但有时我们会接触到一些极其复杂的业务(比如“动画”),其不可避免的还是会用到页面上的一些 DOM 标签。
紧接之前的代码,打开 TodoList.js
文件:
import React, { Component, Fragment } from "react";
import TodoItem from "./TodoItem";
import "./style.css";
class TodoList extends Component {
constructor(props) {
super(props);
this.state = {
inputValue: "",
list: []
};
this.handleInputChange = this.handleInputChange.bind(this);
this.handleBtnClick = this.handleBtnClick.bind(this);
this.handleItemDelete = this.handleItemDelete.bind(this);
}
render() {
return(
<Fragment>
<div>
<label htmlFor="insertArea">请输入要进行的事项:</label>
<input
id="insertArea"
className="input"
value={this.state.inputValue}
onChange={this.handleInputChange}
/>
<button onClick={this.handleBtnClick}>
提交
</button>
</div>
<ul>
{this.getTodoItem()}
</ul>
</Fragment>
)
}
getTodoItem() {
return this.state.list.map((item, index) => {
return(
<TodoItem
key={item}
content={item}
index={index}
itemDelete={this.handleItemDelete}
/>
)
})
}
// 1️⃣当 input 框里的值发生变化后,handleInputChange 里的函数体将会执行;
handleInputChange(e) {
console.log(e.target) // 2️⃣我们可以在控制台打印出 e.target,看看它到底是个什么东西?
const value = e.target.value // 1️⃣-①:首先拿到 input 框里的 value 值;
this.setState(() => ({
inputValue: value
})) // 1️⃣-②:然后用 setState 去修改“数据项”inputValue 里的值;
}
handleBtnClick() {
this.setState((prevState) => ({
list: [...prevState.list, prevState.inputValue],
inputValue: ""
}))
}
handleItemDelete(index) {
this.setState((prevState) => {
const list = [...prevState.list]
list.splice(index, 1)
return {list}
})
}
}
export default TodoList;
当我们在页面的 input 框输入内容时,查看页面控制台, console.log(e.target)
给我们打印出了 input 框的 DOM 节点:
由实践可知,在 React 里,我们可以用 e.target
获取到事件对应“元素”的 DOM 节点。
在 React 里,也可以用另外一种方式(ref)来获取元素对应的 DOM。
2 ref
紧接上边的代码,我们用 ref
去做些修改:
import React, { Component, Fragment } from "react";
import TodoItem from "./TodoItem";
import "./style.css";
class TodoList extends Component {
constructor(props) {
super(props);
this.state = {
inputValue: "",
list: []
};
this.handleInputChange = this.handleInputChange.bind(this);
this.handleBtnClick = this.handleBtnClick.bind(this);
this.handleItemDelete = this.handleItemDelete.bind(this);
}
render() {
return(
<Fragment>
<div>
<label htmlFor="insertArea">请输入要进行的事项:</label>
<input
id="insertArea"
className="input"
value={this.state.inputValue}
onChange={this.handleInputChange}
ref = {(input) => {this.input = input}}
/>
{/*
1️⃣首先,给 input 元素增加一个 ref 属性,它等于一个“箭头函数”。
“箭头函数”的格式为:
1️⃣-①:箭头的左边,“箭头函数”会自动接收到一个“参数”,名字可以随便取(我们取名为 input);
1️⃣-②:箭头的右边,是一个固定写法 this.input = input ;
1️⃣-③:❗️❗️❗️这整行代码的意思为——我构建了一个“引用”ref,这个“引用”叫做 this.input,
它指向 input 框对应的 DOM 节点。
*/}
{/*
2️⃣接着,input 框绑定了一个 handleInputChange 方法。
故,我们去下边找到这个方法,也用 ref 进行相应改写;
*/}
<button onClick={this.handleBtnClick}>
提交
</button>
</div>
<ul>
{this.getTodoItem()}
</ul>
</Fragment>
)
}
getTodoItem() {
return this.state.list.map((item, index) => {
return(
<TodoItem
key={item}
content={item}
index={index}
itemDelete={this.handleItemDelete}
/>
)
})
}
handleInputChange() {
/*
3️⃣在 handleInputChange 方法里,
既然上边通过 ref 方式获取到了事件对应元素的 DOM 节点(this.input),
那这里就可以进行相应地改写:
*/
const value = this.input.value
/*
4️⃣并把之前用 e.target 获取事件对应元素的 DOM 节点的方式注释掉(
注意把上边给“方法”传入的参数 e 也给去掉)。
const value = e.target.value
*/
this.setState(() => ({
inputValue: value
}))
}
handleBtnClick() {
this.setState((prevState) => ({
list: [...prevState.list, prevState.inputValue],
inputValue: ""
}))
}
handleItemDelete(index) {
this.setState((prevState) => {
const list = [...prevState.list]
list.splice(index, 1)
return {list}
})
}
}
export default TodoList;
5️⃣查看下页面效果:
以上效果正常运行,无报错。可见用 ref
来获取 DOM 节点是可以被正常使用的。但,也请你不要滥用!React 是“数据驱动”的框架,非特殊情况下,不要去主动操作 DOM。
况且,用 ref
时,一不小心就会出 bug,比如和 setState
合用。
3 ref
和 setState
合用
紧接上边的代码,打开 TodoList.js
文件:
import React, { Component, Fragment } from "react";
import TodoItem from "./TodoItem";
import "./style.css";
class TodoList extends Component {
constructor(props) {
super(props);
this.state = {
inputValue: "",
list: []
};
this.handleInputChange = this.handleInputChange.bind(this);
this.handleBtnClick = this.handleBtnClick.bind(this);
this.handleItemDelete = this.handleItemDelete.bind(this);
}
render() {
return(
<Fragment>
<div>
<label htmlFor="insertArea">请输入要进行的事项:</label>
<input
id="insertArea"
className="input"
value={this.state.inputValue}
onChange={this.handleInputChange}
ref = {(input) => {this.input = input}}
/>
{/*
1️⃣页面上有一个 button 按钮,当我们“点击”时,
它会去执行 handleBtnClick 方法;
*/}
<button onClick={this.handleBtnClick}>
提交
</button>
</div>
<ul ref={(ul) => {this.ul = ul}}> {/*
3️⃣-①:首先,在 ul 上增加一个 ref 属性(注意写法),
这样的话,当 ul 被创建后,this.ul 就指向 ul 对应
的真实 DOM 节点;
*/}
{this.getTodoItem()}
</ul>
</Fragment>
)
}
getTodoItem() {
return this.state.list.map((item, index) => {
return(
<TodoItem
key={item}
content={item}
index={index}
itemDelete={this.handleItemDelete}
/>
)
})
}
handleInputChange() {
const value = this.input.value
this.setState(() => ({
inputValue: value
}))
}
/*
2️⃣handleBtnClick 方法里的逻辑为:给“数据项”list 增加一个数据,
并把 input 框清空;
*/
handleBtnClick() {
this.setState((prevState) => ({
list: [...prevState.list, prevState.inputValue],
inputValue: ""
}))
/*
3️⃣此时,我有一个需求:在上边的逻辑执行完后,
我想获取到“数据项”list 对应的 DOM 节点。应该怎么做呢?
*/
// 3️⃣-②:然后,试着在控制台打印出相应节点:
console.log(this.ul)
}
handleItemDelete(index) {
this.setState((prevState) => {
const list = [...prevState.list]
list.splice(index, 1)
return {list}
})
}
}
export default TodoList;
3️⃣-③:查看页面控制台的打印信息;
4️⃣❓在这基础上,我有了新的需求:我想打印出 ul 下的 div 长度? 紧接上边代码:
import React, { Component, Fragment } from "react";
import TodoItem from "./TodoItem";
import "./style.css";
class TodoList extends Component {
constructor(props) {
super(props);
this.state = {
inputValue: "",
list: []
};
this.handleInputChange = this.handleInputChange.bind(this);
this.handleBtnClick = this.handleBtnClick.bind(this);
this.handleItemDelete = this.handleItemDelete.bind(this);
}
render() {
return(
<Fragment>
<div>
<label htmlFor="insertArea">请输入要进行的事项:</label>
<input
id="insertArea"
className="input"
value={this.state.inputValue}
onChange={this.handleInputChange}
ref = {(input) => {this.input = input}}
/>
<button onClick={this.handleBtnClick}>
提交
</button>
</div>
<ul ref={(ul) => {this.ul = ul}}>
{this.getTodoItem()}
</ul>
</Fragment>
)
}
getTodoItem() {
return this.state.list.map((item, index) => {
return(
<TodoItem
key={item}
content={item}
index={index}
itemDelete={this.handleItemDelete}
/>
)
})
}
handleInputChange() {
const value = this.input.value
this.setState(() => ({
inputValue: value
}))
}
handleBtnClick() {
this.setState((prevState) => ({
list: [...prevState.list, prevState.inputValue],
inputValue: ""
}))
/*
4️⃣-②:既然想打印 ul 中 div 的长度,
我们可以借助原生 JS 中的相关方法:
*/
console.log(this.ul.querySelectorAll("div").length)
/*
4️⃣-①:先把这行代码注释掉,我们有了新的需求;
console.log(this.ul)
*/
}
handleItemDelete(index) {
this.setState((prevState) => {
const list = [...prevState.list]
list.splice(index, 1)
return {list}
})
}
}
export default TodoList;
4️⃣-③:回到页面控制台查看 this.ul.querySelectorAll("div").length
打印出的 div 长度;
❌问题出现了:
- 我输入
Oli
后,控制台预期应该打印出长度1
,它却打印出0
; - 我再输入
qdywxs
后,控制台预期应该打印长度2
,它却打印出1
。
即,控制台打印出的 div 长度总是比实际长度少 1 个!
❓这是为什么呢? 答:
- 你不去操作 DOM 就没事,
ref
和setState
结合使用,就会出现这种问题; - 这个问题可以解吗?——可以,这个问题我们之前就涉及过。
React 中,setState 被设计成一个“异步函数”。
即,在本例中,当调用 handleBtnClick
的时候, setState
中的函数体并不会立即执行,它会等一会儿再去执行。
而 console.log(this.ul.querySelectorAll("div").length)
很有可能是在 setState
底层运行之前去运行的(根据页面效果来看,确实是这样)。
❓这种情况怎么解呢?
setState 允许我们在它里边添加第二个参数(也是一个函数),即,将 console.log(this.ul.querySelectorAll("div").length)
以“函数”的形式传给 setState
:
handleBtnClick() {
this.setState((prevState) => ({
list: [...prevState.list, prevState.inputValue],
inputValue: ""
}), () => {
console.log(this.ul.querySelectorAll("div").length)
}) /*
❓第二个“函数”形式的“参数”什么时候执行呢?
答:会等到 setState 这个“异步”的方法完全执行好了后,
作为“回调函数”的第二个参数才会被执行。
*/
}
回到页面,看下效果(打印出的 div 长度正确,页面也没报错):
祝好,qdywxs ♥ you!
转载自:https://juejin.cn/post/7289662055195623424