React三大核心属性state、props、refs,前边已经学过了state和props,现在开始最后一个refs的学习。
再从小需求说起
建立在业务基础上的技术才是最有价值的,假设有这样一个组件,一个输入框和一个按钮,需要在输入框中输入内容后,点击按钮则弹窗展示输入的内容。
需求比较简单,根据以往的经验,可能会写出这样的一段代码:
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类型的,那么上边的代码就可以改成这样:
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。
当然了,上边代码也可以使用解构写法优化自定义方法中的代码,例如:
showInput=()=>{
let {input1} = this.refs;
alert(input1.value)
}
string用法看起来很简单,但是官网最新的文档已经说这是过时用法了,存在一些问题,不建议再使用,建议用函数式和createRef方式,下边就看看函数式
函数类型的refs
函数式分为内联函数和外部引用函数,使用内联函数式写法,上边代码就可以进一步改成这样:
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,代码则可以改成这样:
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这种写法必须在外边先进行定义,如下边这样:
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 方式处理。
如果是类似上边示例中事件发生位置和数据节点不同的情况,实际上也可以通过后续受控组件和非受控组件的内容进行处理。
|