React系统学习4(組件三大核心属性之refs)

React三大核心属性state、props、refs,前边已经学过了state和props,现在开始最后一个refs的学习。

再从小需求说起

建立在业务基础上的技术才是最有价值的,假设有这样一个组件,一个输入框和一个按钮,需要在输入框中输入内容后,点击按钮则弹窗展示输入的内容。

需求比较简单,根据以往的经验,可能会写出这样的一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Demo extends React.Component{

showInput=()=>{
alert(document.getElementById('input1').value)
}

render() {
return (
<div>
<input id="input1" type="text" placeholder="点击按钮弹出输入内容"/>
<button onClick={this.showInput}>点击按钮弹出输入内容</button>
</div>
)
}
}

ReactDOM.render(<Demo />,document.getElementById('test'))

如果浏览器访问,会发现确实达到了想要的效果。

但是这里却有一个问题,那就是在React中除开特定的几个地方只能使用document.getElementById这种直接操作真实DOM的情况外,大多数时候是不应该直接操作真实DOM的。

原因之前应该也提到过,React会管理一个虚拟DOM,然后会把虚拟DOM渲染成真实DOM,但是渲染过程中并不是每次都全部内容渲染,而是会根据一定的算法,最终尽可能少的生成新的真实DOM,从而提升效率。

在React中提供了一个属性refs,就可以解决上边的问题。

string类型的refs

refs的用法有好几种,最简单的就是string类型的,那么上边的代码就可以改成这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Demo extends React.Component{

showInput=()=>{
alert(this.refs.input1.value)
}

render() {
return (
<div>
<input ref="input1" type="text" placeholder="点击按钮弹出输入内容"/>
<button onClick={this.showInput}>点击按钮弹出输入内容</button>
</div>
)
}
}

ReactDOM.render(<Demo />,document.getElementById('test'))

和之前不同的就是两个地方,一个是把id="input1"换成了ref="input1",另一个就是把alert(document.getElementById('input1').value)改成了alert(this.refs.input1.value)

很显然,在这里就没有再直接操作真实DOM。

当然了,上边代码也可以使用解构写法优化自定义方法中的代码,例如:

1
2
3
4
showInput=()=>{
let {input1} = this.refs;
alert(input1.value)
}

string用法看起来很简单,但是官网最新的文档已经说这是过时用法了,存在一些问题,不建议再使用,建议用函数式和createRef方式,下边就看看函数式

函数类型的refs

函数式分为内联函数和外部引用函数,使用内联函数式写法,上边代码就可以进一步改成这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Demo extends React.Component{

showInput=()=>{
alert(this.input1.value)
}

render() {
return (
<div>
<input ref={(node)=>{this.input1=node}} type="text" placeholder="点击按钮弹出输入内容"/>
<button onClick={this.showInput}>点击按钮弹出输入内容</button>
</div>
)
}
}

ReactDOM.render(<Demo />,document.getElementById('test'))

而使用外部引用函数类型的refs,代码则可以改成这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Demo extends React.Component{

showInput=()=>{
alert(this.input1.value)
}

bindRef=(node)=>{
this.input1=node
}

render() {
return (
<div>
<input ref={this.bindRef} type="text" placeholder="点击按钮弹出输入内容"/>
<button onClick={this.showInput}>点击按钮弹出输入内容</button>
</div>
)
}
}

ReactDOM.render(<Demo />,document.getElementById('test'))

上边两份代码的区别就在于,第一个直接在ref后边定义了一个匿名的函数,而第二个则是先声明了一个函数,然后再在ref后边进行引用。

这两种写法看起来第一种更加简洁,可能也是实际用的更多的。

但是实际上有一个小细节,那就是第一种写法在每次重新render渲染的时候都会加载两次这个函数,当然了,大多数时候可能也没有太大影响。

createRef类型的refs

实际上函数式应该是用的最多的了,因为createRef这种写法必须在外边先进行定义,如下边这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Demo extends React.Component{

showInput=()=>{
alert(this.bindRef.current.value)
}

bindRef=React.createRef()

render() {
return (
<div>
<input ref={this.bindRef} type="text" placeholder="点击按钮弹出输入内容"/>
<button onClick={this.showInput}>点击按钮弹出输入内容</button>
</div>
)
}
}

ReactDOM.render(<Demo />,document.getElementById('test'))

可能有人会说,为什么不直接把React.createRef()放在ref后边呢?

因为实际上发现这样写的时候根本无效,至于为什么,暂时就不太清楚。

正是由于这个原因,相比内联函数的写法似乎就要复杂一些,所以可能大多数时候都选择内联函数的写法。

最后做一个总结,refs三种用法,看似最简单的可能会被弃用,最常用的应该是内联函数方式。

同时,官网说不要过度使用refs,否则也可能造成性能问题,也就是说实际开发时能不用它就不用,而是该用react中的其他一些方式。

因为从上边代码中也可以看出来,我们用refs就是为了拿到某个节点,然后得到数据。

实际上这种需求,如果事件发生的位置和数据节点就是一个,完全可以通过event.target方式处理。

如果是类似上边示例中事件发生位置和数据节点不同的情况,实际上也可以通过后续受控组件和非受控组件的内容进行处理。

推荐文章