本文已参与「新人创作礼」活动,一起开启掘金创作之路。详情
refs是什么
Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。
我们可以给元素添加一个ref属性,通过设置的ref访问该子元素的实例。这个元素可以是直接的html标签,也可以是自定义的class组件或函数组件。
refs的使用
1. 在class组件中使用createRef
我们使用createRef创建ref有以下几种情况:
1、直接绑定到标签元素
:
①使用React.createRef
创建ref
②将创建的ref设置在元素上
③通过ref.current
访问元素
import React, {Component, createRef} from 'react';
class RefsTest extends Component{
constructor(props) {
super(props);
this.nameRef = createRef();
}
bthClick() {
console.log(this.nameRef.current, this.nameRef.current.value)
}
render() {
return (
<div>
<div>姓名:<input ref={this.nameRef} /></div>
<button onClick={this.bthClick.bind(this)}>提交</button>
</div>
)
}
}
我们可以看到上面示例运行后拿到了input元素,并且可以访问该元素的方法和属性。
2、父组件class组件时,访问子元素也为class组件
①我们可以直接在class组件中访问一个子class组件的实例
②我们可以访问这个子class组件的方法/属性
③我们可以通过访问子class组件的方法获取到子class中的某些具体子元素
我们看一下这个示例:
import React, {Component, createRef} from 'react';
class RefsTest extends Component{
constructor(props) {
super(props);
this.ageRef = createRef()
}
bthClick() {
console.log(this.ageRef.current) // 访问class组件
console.log(this.ageRef.current.getInputRefValue()) //访问组件中的方法,获取到子class组件中某个元素的值
}
render() {
return (<div>
<AgeInput ref={this.ageRef} />
<button onClick={this.bthClick.bind(this)}>提交</button>
</div>)
}
}
class AgeInput extends Component{
constructor(props) {
super(props);
this.inputRef = createRef();
}
getInputRefValue() {
return this.inputRef.current.value
}
render() {
return <div>
年龄:<input ref={this.inputRef}></input>
</div>
}
}
我们在RefTest组件
中拿到了AgeInput组件
,然后通过AgeInput组件
的getInputRefValue
方法又访问到了AgeInput组件
的子元素input
框的属性。通过该方法我们实现了组件跨层级通信。
2. Refs 与函数组件 (hook用法)
默认情况下,我们不能在函数组件上使用 ref
属性,因为它们没有实例。在讲hook用法前,我们先看一下class组件中的函数组件怎么使用ref:
1、父组件为class组件,访问子组件为函数组件
:
①我们直接用createRef
创建ref
②将函数组件使用React.forwardRef
处理生成新的组件
③将创建好的ref设置在生成的新的组件上
④在函数组件中接收ref绑定到实际需要访问的元素上
⑤通过ref.current
访问元素
class RefsTest extends Component{
constructor(props) {
super(props);
this.ageRef = createRef()
}
bthClick() {
//访问函数组件
console.log(this.ageRef.current, this.ageRef.current.value)
}
render() {
const AgeWithRef = React.forwardRef(AgeInput)//这一步很重要
return (<div>
<AgeWithRef ref={this.ageRef} />
<button onClick={this.bthClick.bind(this)}>提交</button>
</div>)
}
}
function AgeInput(props, ref) {
return(
<div>
年龄:<input ref={ref}></input>
</div>
)
}
我们通过这个示例,可以发现访问class组件与函数组件的不同:
①函数组件必须使用React.forwardRef
做ref转发处理,否则直接使用会报错
②函数组件接收了ref之后是直接绑在其内部需要的元素上,无法访问整个函数组件实例;而class组件是可以直接拿到整个组件实例的,需要访问class组件中的子元素,需要在子的class组件中再使用createRef
生成绑定目标元素input框,自行通过组件内的放法getInputRefValue将这个实例传递到最外层RefTest
组件。
2、在父组件为函数组件时:
我们在函数组件中使用ref只能指向一个DOM元素或者class组件
在函数组件中我们就需要用**hook
的方法**了
①使用useRef
初始化一个ref
②将这个ref设置在需要的元素上
③通过定义的ref.current
访问元素
import React, {useRef} from 'react';
function RefsTest() {
const nameRef = useRef(null)
const btnClick = () => {
console.log(nameRef.current, nameRef.current.value)
}
return(
<div>
姓名:
<input ref={nameRef}></input>
<button onClick={btnClick}>提交</button>
</div>
)
}
3.使用callback方法
①初始化constructor中定义ref为null
②定义一个回调函数,其回调参数就是设置的需要的元素
③在需要的元素上设置ref属性为这个回调函数
④访问该元素直接使用定义的this.xxref
,不需要再.current
属性才拿到元素了
class RefsTest extends Component{
constructor(props) {
super(props);
// 1.初始化ref
this.nameRef = null;
this.state = {
num: 0
}
}
// 2.定义回调函数
setNameRef = el => {
// callback方式可以接收一个element,这个el就是当前的input框。
console.log('定义callback函数设置ref', el)
this.nameRef = el
}
componentDidMount() {
// React会在组件挂载时,调用ref的回调函数并传入DOM元素,当卸载时也调用回调函数并传入参数为null
// 在componentDidMount和componentDidUpdate触发前,React会保证refs一定是最新的
this.nameRef.focus();//实现input框获取焦点
}
bthClick() {
console.log(this.nameRef, this.nameRef.value)// 没有.current
}
changeState = () => {
console.log('state--组件重新渲染')
this.setState({
num: this.state.num+1
})
}
render() {
return (<div>
{/* 3.元素上设置回调函数 */}
<div>
姓名:
<input ref={this.setNameRef} />
</div>
<button onClick={this.bthClick.bind(this)}>提交</button>
<button onClick={this.changeState}>更新组件{this.state.num}</button>
</div>)
}
}
绑定ref到input上的回调函数有直接行内写法:
<input ref={el=>{
console.log('行内设置ref', el)
this.nameRef = el
}} />
这种行内写法和定义callback方法有什么区别呢?
①当我们点击更新组件的按钮时,通过setState触发组件重新渲染可以看到只有这个更新组件
这个按钮事件的输出
②当我们把input框的ref改成行内写法后再触发组件重新渲染时看到输出:
看出来每次重新渲染时都会执行这个ref组件的卸载再挂载。其原因在于行内设置属性为函数,每次重新渲染时相当于重写定义了一次ref属性的这个函数,造成了React进行diff时判断时其属性是不一样的,所以无法组件复用,只能把这个节点删除再新增,所以就会出现上面的打印结果。
所以在使用时我们应该注意避免行内写法,大部分情况下我们的callback中并没有太多的逻辑操作,基本上只是ref的赋值,所以它用行内写法也是无关紧要的。但是为了性能优化考虑,我们还是需要主要避免。
使用refs需要注意的几点
- 无法拿到整个函数组件,因为函数组件没有实例。只能使用
forwardRef
将其转发到函数组件内的某些元素上 - React之前版本的String 类型的 Refs在后续会被弃用,应当避免通过String方法使用ref了
- 使用callback方式使用ref时注意避免使用行内写法