此文章是本人在学习React的时候,写下的学习笔记,在此纪录和分享。此为第六篇,主要介绍react中的refs。
目录
1.refs基本使用
1.1字符串类型ref小案例
2.回调形式的ref
2.1回调形式ref小案例
2.2回调ref中调用次数问题
3.createRef
3.1createRef的使用
3.2createRef的“专人专用”
4.ref总结
1.refs基本使用
组件内的标签可以定义ref属性来标识自己。
1.1字符串类型ref小案例
首先我们写一个小案例,来了解refs。我们写一个自定义组件,写两个输入框,点击按钮提示第一个输入框的值,当第二个文本框失去焦点的时候,提示第二个文本框的值。
步骤如下:
1.在render函数构建虚拟dom时,我们使用ref为input框添加标识,第一个input叫做input1,第二个叫input2,比如说
<input ref='input1' type="text" placeholder="点击按钮提示数据"/>
注意,使用的是ref来添加表示,而不是refs,不要多写一个s。
2.然后我们为相应的按钮,和第二个input框设置事件,分别是点击事件和失去焦点事件。如下:
<button onClick={this.showData}> <input ref='input2' onBlur={this.showData2} type="text"/>
注意在react中的事件与js中不同,比如说点击事件onclick,在react里面on后的首字母大写,与js做区分。基本上在react里面事件都是在on后首字母大写。
3.下一步我们写事件处理函数,showData和showData2。由于我们给input框设置了ref添加标识,那么所以使用ref添加标识的信息,全部存储在本组件实例的refs属性里面,所以我们在组件内使用的时候,直接this.refs.使用ref设置的标识名即可使用,也比较推荐使用解构赋值来使用。如下,对refs属性解构赋值,拿取我们想要的input1:
const {input1} = this.refs
如此这个inpput1就代表了第一个输入框,我们就可以对第一个文本框的内容进行输入。
代码如下:
<script type="text/babel">
//创建组件
class Demo extends React.Component {
showData = ()=>{
const {input1} = this.refs
alert(input1.value)
}
showData2 = ()=>{
const {input2} = this.refs
alert(input2.value)
}
render() {
return (//构建虚拟dom
<div>
<input ref='input1' type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
<input ref='input2' onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
//渲染组件到界面
ReactDOM.render(<Demo/>, document.getElementById('test1'))
</script>
2.回调形式的ref
2.1回调形式ref小案例
上面那个案例是字符串类型的refs,虽然非常简单,但在react官方文档内,不推荐使用这种方式,会出现一些效率上的问题,甚至以后会废弃。所以上面那个案例我们只做refs的学习的入门。
所以现在我们来学习回调形式的ref。
1.我们尝试把 ref='input1' 改为箭头函数类型:ref={(a)=>{console.log(a)}},此函数参数为a,再在函数体内打印a,看看a是什么东西。
字符串形式:<input ref='input1' type="text" placeholder="点击按钮提示数据"/>
函数形式:<input ref={(a)=>{console.log(a)}} type="text" placeholder="点击按钮提示数据"/>
打印结果:
可见,参数a就是本dom节点,就是这个input框当作参数传入了ref。因为箭头函数并没有自己的this,它会把外层render函数的this作为自己的this,而reander函数的this指向组件构造的实例,所以本箭头函数就能和实例相关联,此ref所处的dom节点挂载到实例上并且取名为“input1”。
所以这个形参叫做a就不太合适了,适合叫做currentNode(当前节点),见名知意,或简略称为c。结合箭头函数的特性,只有一个参数,那么可以省略参数的小括号,函数体也就一句,也可以省略箭头函数右侧花括号。所以ref精简如下:
ref={c => this.input1 = c}
2.事件函数会发生改变。原来是 const {input2} = this.refs ,而现在是 const {input2} = this ,因为此ref所处的dom节点挂载到实例上,所以直接写this即可。
代码如下:
<script type="text/babel">
//创建组件
class Demo extends React.Component {
showData = () => {
const {input1} = this
alert(input1.value)
}
showData2 = () => {
const {input2} = this
alert(input2.value)
}
render() {
return (//构建虚拟dom
<div>
<input ref={c => this.input1 = c} type="text" placeholder="点击按钮提示数据" />
<button onClick={this.showData}>点我提示左侧的数据</button>
<input ref={c => this.input2 = c} onBlur={this.showData2} type="text" placeholder="失去焦点提示数据" />
</div>
)
}
}
//渲染组件到界面
ReactDOM.render(<Demo />, document.getElementById('test1'))
</script>
2.2回调ref中调用次数问题
我们先上代码:
class Demo extends React.Component {
showInfo = ()=>{
const {input1} = this
console.log(input1);
}
render() {
return (//构建虚拟dom
<div>
<input ref={c => this.input1 = c} type="text" />
<button onClick={this.showInfo}>点我提示输入的数据</button>
</div>
)
}
}
//渲染组件到界面
ReactDOM.render(<Demo />, document.getElementById('test1'))
再引入react官网关于回调ref的说明:
如果 ref
回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null
,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。
注意,我们上面所写代码,将ref回调函数写在input标签内,这就是以内联函数的方式定义ref回调函数,符合官网的说明。但是,在更新过程中才会被执行两次,一次null一次传入参数dom元素,第一次执行render渲染dom是初始化,并非更新。
所以我们在案例上面再加上数据更新的案例,让这个案例有更新状态,再来验证这个回调次数的问题。新加入的案例,就是我们之前写过的,炎热与凉爽切换的案例。另外在ref的回调函数内打印一下回调函数的参数c。代码如下:
//创建组件
class Demo extends React.Component {
state = { isHot: false }
showInfo = () => {
const { input1 } = this
alert(input1.value)
}
changWeather = ()=>{
const {isHot} = this.state
this.setState({isHot:!isHot})
}
render() {
const { isHot } = this.state
return (//构建虚拟dom
<div>
<h2>今天天气很{isHot ? '炎热' : '凉爽'}</h2>
<input ref={c => {this.input1 = c;console.log('@',c)}} type="text" /><br/><br/>
<button onClick={this.showInfo}>点我提示输入的数据</button><br/><br/>
<button onClick={this.changWeather}>点我切换天气</button>
</div>
)
}
}
//渲染组件到界面
ReactDOM.render(<Demo />, document.getElementById('test1'))
效果如下,刚打开页面:
点击切换按钮切换页面显示文字后:
这正好印证了官网所言。更新时,第一次回调函数参数为null,第二次才把dom节点当参数传进来。但这并不影响功能的正常。
解释:页面刚加载好的时候,是没有这个情况的,是因为调用render函数,在里面发现回调函数形式的ref,接着根据ref函数内所写把相应的dom节点当作参数传入。
但是在更新的时候,state会发生改变,state会驱动页面显示,重新调用render函数。在重新调用render时,发现虚拟dom中回调函数式的ref,但react不确定之前调用这个ref的时候,接收的参数,函数执行发生了什么,所以直接先清空这个ref,传参为null,然后再调用这个ref传入当前dom节点参数。
避免这种问题:官网所言:通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。
那什么是class绑定函数的方式呢?我们来修改上面的代码:
原来的代码:
<input ref={c => {this.input1 = c;console.log('@',c)}} type="text" />
这样的将回调函数式ref写在dom节点内,我们称其为内联式ref。
将这段代码进行修改:
<input ref={this.saveInput} type="text" />
并且将saveInput函数放在实例自身上。
saveInput = (c) => {
this.input1 = c
console.log("@", c);
}
这样就变成了class的绑定函数方式,它不会影响功能的正常运行。这种情况,就不会发生更新后,它被调用两次,第一次是null的情况了。
3.createRef
3.1createRef的使用
React.createRef是react的一个api,调用后可以返回一个容器,该容器可以存储被ref所标识的节点。所以我们使用它时,可以创建createRef容器赋值给myRef并且挂载到实例上:
myRef = React.createRef()
然后直接在render函数相应的标签内使用:
<input ref={this.myRef} type="text" />
这样就相当于把input这个dom节点当作ref的参数,将这个ref放进myRef这个容器里面。
来看代码:
//创建组件
class Demo extends React.Component {
/*
React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点
*/
myRef = React.createRef()//创建容器赋值给myRef并且挂载到实例上
showInfo = () => {
console.log(this.myRef);
}
render() {
return (//构建虚拟dom
<div>
<input ref={this.myRef} type="text" />
<button onClick={this.showInfo}>点我提示输入的数据</button>
</div>
)
}
}
//渲染组件到界面
ReactDOM.render(<Demo />, document.getElementById('test1'))
我们打印this.myRef看看输出结果:如图所示,打印出的是一个对象,里面的current是我们放入容器的input节点。
然后打印this.myRef.current:如图所示,打印出了input节点。
最后打印this.myRef.current.value:如图所示打印出了input框内的值。
3.2createRef的“专人专用”
注意,虽然createRef作为一个容器,但它是“专人专用”的,只能往一个容器内放置一个。我们可以尝试写一个案例,往createRef容器里面塞两个ref。
上面的那个案例,我们给button也设置上createRef容器:
<input ref={this.myRef} type="text" />
<button ref={this.myRef} onClick={this.showInfo}>点我提示输入的数据</button>
并且打印输出,console.log(this.myRef);
结果如下:
显而易见,createRef内只能存在一个ref,后面的会顶替掉前面的。
所以我们得多创建几个createRef,保证每个“人”都有createRef用。我们创建两个createRef,分别赋值给myRef和myRef2。为两个input分别分配myRef和myRef2,在showInfo和showInfo2内对两个容器做不同的处理。
代码如下:
//创建组件
class Demo extends React.Component {
/*
React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点
*/
myRef = React.createRef()//创建容器赋值给myRef并且挂载到实例上
myRef2 = React.createRef()
showInfo = () => {
alert(this.myRef.current.value);
console.log(this.myRef);
}
showInfo2 = () => {
alert(this.myRef2.current.value)
}
render() {
return (//构建虚拟dom
<div>
<input ref={this.myRef} type="text" placeholder="点击按钮提示内容" />
<button onClick={this.showInfo}>点我提示输入的数据</button>
<input ref={this.myRef2} onBlur={this.showInfo2} type="text" placeholder="失去焦点提示内容" />
</div>
)
}
}
//渲染组件到界面
ReactDOM.render(<Demo />, document.getElementById('test1'))
这样在需要多个createRef容器的情况下,就可以正常的使用。但也有弊端,就是reander函数内用几个createRef,上面实例内就需要创建几个createRef。但这个createRef还是react官方最推荐的ref使用方式。
4.ref总结
在学完三种ref的使用方式后,我们总结一下:
1.尽量避免使用字符串形式的ref,但尽管写了字符串形式的ref,也不会有太大问题。
2.回调形式的ref稍微麻烦一些。不要太纠结回调函数的回调执行次数问题,内联式的回调函数可以写,官网说了:将 ref 的回调函数定义成 class 的绑定函数的方式,但大多数情况无关紧要。
3.createRef是最麻烦的,但目前来说是react官方最推荐的。