首先要明确的一件事是:在类组件和原生组件中,可以直接通过ref属性拿到元素的真实DOM,而在函数类型组件中,如果直接给组件绑定ref属性是没有效果的。
例如下面的代码:
//子组件
function Child(props){
return (
<div>
<button>child</button>
</div>
)
}
function MyCom(props){
const childButton = useRef();
const click = () => {
console.log(childButton)
//{current: undefined}拿不到真实的dom
}
return (
<div>
//绑定ref
<Child ref={childButton} />
<button onClick={click} >123</button>
</div>
)
}
这个问题会在后面说,先说一下在类组件中,ref的几种使用方式:
每种方式如何使用,这里也不细说了。主要了解的是在Hook里面,useRef启到了什么作用?
useRef
返回一个可变的 ref 对象,其.current
属性被初始化为传入的参数(initialValue
)。返回的 ref 对象在组件的整个生命周期内持续存在。
这句话是官网上用来描述useRef的。也就是说useRef并非单单用来处理获取真实dom,而是像state一样的用来管理数据的API。
只不过它比较常见的使用方式是用来管理真实的dom元素。
在函数组件中,我们可以通过const或者let直接定义变量,也可以通过useState定义变量,现在还可以通过useRef定义变量。而这几种定义变量方式的区别在于什么?\
function MyCom(props){
let num = 0 //每次更新都初始化
const [stateNum,setStateNum] = useState(0)
const refNum = useRef(0);
const click = () => {
num++; //不会更新组件
refNum.current ++; //不会更新组件
setStateNum(stateNum + 1) //会更新组件
}
return (
<div>
<span>{num}</span>
<br />
<span>{refNum.current}</span>
<br />
<span>{stateNum}</span>
<br />
<button onClick={click} >123</button>
</div>
)
}
基于上面的代码,我们解释一下num,stateNum,refNum之间的不同之处:\
所以只有setState可以让组件更新,而更新后,refNum和stateNum可以保存上一次更新的记录,而num要重新进行初始化
refNum类似于在类组件中,下面的写法:
class MyCom extends React.Component {
num = 0;
click = () => {
this.num++;
this.setState({})
}
render(){
return (<div onClick={this.click}>{this.num}</div>)
}
}
回到第一个问题,在函数组件里面,并不能直接通过ref来获取真实的DOM元素。所以React提供了forwardRef方法,能将函数组件里的ref进行转发。
const Child = React.forwardRef((props,ref) => {
return (
<div>
<button ref={ref}>child</button>
</div>
)
})
function MyCom(props){
const childButton = useRef();
const click = () => {
console.log(childButton)
}
return (
<div>
<Child ref={childButton} />
<button onClick={click} >123</button>
</div>
)
}
forward接受的回调函数接收两个参数,第一个参数是props,第二个就是ref。而两者都来自于父组件。通过forward,父组件可以拿到子组件任何位置元素的真实DOM。
上面的代码,通过useRef创建了变量childButton。最终通过ref将子组件button的DOM元素绑定给了childButton,不知道你是否尝试过另一种创建childButton的方式。
const childButton = {current: null};
其实这种方式是可以的。也就是说,想要拿到真实DOM元素,并非一定要是用useRef,也可以通过定义变量{current: null}
的方式。
而两者的区别在于(官网给出的解释):
而
useRef()
和自建一个{current: ...}
对象的唯一区别是,useRef
会在每次渲染时返回同一个 ref 对象。
通常来讲,我们拿到真实DOM之后,可能需要调用它自身的某个方法。比如input输入框的focus方法。
而通过上面的方式,会拿到整个真实DOM,基于这种情况,React提供了一个hook,来自定义暴露给父组件的ref。
const Child = React.forwardRef((props,ref) => {
const inputRef = useRef();
//自定义focus和getType方法,并且将对象绑定给父组件传来的ref上
useImperativeHandle(ref,() => (
{
focus: () => {
inputRef.current.focus()
},
getType: () => {
console.log('input')
}
}
))
return (
<div>
<input ref={inputRef} />
</div>
)
})
function MyCom(props){
const childRef = {current: null};
const click = () => {
childRef.current.focus()
childRef.current.getType();
}
return (
<div>
<Child ref={childRef} />
<button onClick={click} >123</button>
</div>
)
}
以上就是和useRef相关的内容,具体的细节可以通过查找对应的API去学习。官网上的文档还是很清晰的