React组件三大核心属性 - Refs
# 一、refs的理解
组件内的标签可以定义ref属性来标识自己,就比如原生中通过document获取dom元素的方法,这里ref获取的是虚拟DOM转换为真实的DOM,而不是获取虚拟DOM。
此属性可以是一个由 React.createRef()
函数 (opens new window)创建的对象、或者一个回调函数
、或者一个字符串(遗留 API)
。当 ref
属性是一个回调函数时,此函数会(根据元素的类型)接收底层 DOM 元素或 class 实例作为其参数。这能够让你直接访问 DOM 元素或组件实例。
为什么是Refs?
refs存放的是所有使用了ref标识的对象
何时使用 Refs?
下面是几个适合使用 refs 的情况:
- 管理焦点,文本选择或媒体播放。
- 触发强制动画。
- 集成第三方 DOM 库。
避免使用 refs 来做任何可以通过声明式实现来完成的事情。
举个例子,避免在 Dialog
组件里暴露 open()
和 close()
方法,最好传递 isOpen
属性。
注意
不要过度使用Refs
# 二、字符串形式的ref
定义: 直接在标签中写上ref="xxx"
使用: this.refs.xxx
// 创建组件
class Demo extends React.Component {
render() {
return (
<div>
<input ref="input1" type="text" placeholder="点击按钮提示数据" />
<button onClick={this.showData}>点击提示左侧的数据</button>
</div>
)
}
showData = () => {
console.log(this); // Demo实例
// 解构
const { input1 } = this.refs;
console.log('input1 ==>', input1); // DOM
console.log('value ==>', input1.value); // 输入框的值
}
}
// 渲染组件到页面
ReactDOM.render(<Demo />, document.getElementById('test'));
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
图示
注意尽量要同名,否则会替换前一个ref
这种形式也被称为过时的API,官网也推荐我们使用回调函数
或者createRef()
代替。
但是吧,我们也得知道有这东西哈。官网地址请点击 (opens new window)
# 三、回调函数形式的ref
什么是回调函数?
自己定义的函数
自己没有调用
最后别人帮你调用
我们这里直接使用箭头函数即可,箭头函数本身没有this,往外找是实例对象, 把当前的ref所在的节点当作函数的实参传给了你所定义的那个属性
class Demo extends React.Component {
render() {
// 箭头函数没有this,往外找是Demo的实例对象, 把当前的ref所在的节点当作函数的实参传给了input1属性
return (
<div>
<input ref={currentNode => this.input1 = currentNode} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点击提示左侧的数据</button>
</div>
)
}
showData = () => {
const {input1} = this;
console.log(this);
console.log(input1.value);
}
}
ReactDOM.render(<Demo />, document.getElementById('test'));
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
图示
# 四、createRef创建的ref
React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,不同节点不能使用同一个,是覆盖操作,会替换掉之前的那个ref。
class Demo extends React.Component {
// React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,不同节点不能使用同一个,是覆盖操作,会替换掉之前的那个ref
myRef = React.createRef();
myRef2 = React.createRef();
render() {
return (
<div>
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点击提示左侧的数据</button>
<input ref={this.myRef2} onBlur={this.blurData} type="text" placeholder="失去提示数据"/>
</div>
)
}
showData = () => {
console.log(this);
console.log(this.myRef); // {current: input} ---- current不能改,React规定是这样
console.log('myRef ==> ', this.myRef.current.value)
}
blurData = () => {
console.log('myRef2 ==> ',this.myRef2.current.value)
}
}
ReactDOM.render(<Demo />, document.getElementById('test'));
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
图示
如果
ref
回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数null
,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。
# 五、回调ref中调用次数的问题
1、内联形式的回调
在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。
2、class 的绑定函数
通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,因为class类函数是已经放在了this(实例对象)上,就算就重新渲染this中已经存在了,就不会重新赋值。但是大多数情况下它是无关紧要的。
也就是说这两者区别是有的,但其实没啥大问题。
案例:当切换天气的时候看函数调用了几次?
class Demo extends React.Component {
state = {
isHot: true
}
render() {
const { isHot } = this.state;
return (
<div>
<p>今天天气很{isHot ? '炎热' : '凉爽'}</p>
{/* 内联的回调 */}
{/*<input ref={currentNode => {this.input1 = currentNode;console.log('currentNode ==>', currentNode);}} type="text" placeholder="点击按钮提示数据"/> */}
<input ref={this.saveInput} type="text" placeholder="点击按钮提示数据" />
<button onClick={this.changeWeather}>点击改变天气</button>
<button onClick={this.showData}>点击提示左侧的数据</button>
</div>
)
}
showData = () => {
const { input1 } = this;
alert(input1.value)
}
// 绑定class类的函数
saveInput = (currentNode) => {
this.input1 = currentNode;
console.log('currentNode ==>', currentNode);
}
// 改变天气,存放在实例对象上的函数
changeWeather = () => {
const { isHot } = this.state;
this.setState({
isHot: !isHot
})
}
}
ReactDOM.render(<Demo />, document.getElementById('test'));
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
图示
# 六、小总结
- 创建ref的三种方式:分别是
字符串形式
,回调函数
,createRef()
- 命名的时候应尽量避免重复命名,否则会产生一个覆盖操作
- 不要过度的使用ref