React和Vue一样,不建议操作DOM,但是如果我们在某个场景必须操作DOM,那么也可以使用refs来实现。
refs的基本使用
refs的创建
refs是通过React.createRef()来创建的,通过使用ref属性来对应某个元素。一般在constructor中赋值给某个实例属性来达到多次复用的作用
class App extends React.Component{
render(){
return <MyComponent />
}
}
class MyComponent extends React.Component{
constructor(props){
super(props);
// 创建ref
this.myRef=React.createRef();
// 绑定方法
this.btnClick=this.btnClick.bind(this);
}
btnClick(){
// 打印ref对应的元素
// 元素被放在ref的current属性中
console.log(this.myRef.current);
}
render(){
return <button ref={this.myRef} onClick={this.btnClick}>123</button>
}
}
上面的代码,点击按钮就会打印出对应的元素
访问refs
当refs被传递给render中的元素的时候,可以通过current属性来访问,如同上面的代码一样
btnClick(){
// 打印ref对应的元素
// 元素被放在ref的current属性中
console.log(this.myRef.current);
}
ref的current属性根据其放在不同的标签上有所不同
- 当放在html元素上时,current属性指向html元素
- 当放在自定义标签时,current属性指向挂载的实例
上面的代码就是第一种情况,对于第二种情况,修改上面的代码
class App extends React.Component{
render(){
return <MyComponent />
}
}
class MyComponent extends React.Component{
constructor(props){
super(props);
// 创建ref
this.myRef=React.createRef();
// 绑定方法
this.btnClick=this.btnClick.bind(this);
}
btnClick(){
// 打印ref对应的元素
// 元素被放在ref的current属性中
console.log(this.myRef.current);
}
render(){
return <div>
<button onClick={this.btnClick}>123</button>
{/* ref指向自定义组件 */}
<ChildComponent ref={this.myRef} />
</div>
}
}
class ChildComponent extends React.Component{
render(){
return <div>111</div>
}
}
点击后发现和刚才结果不同
打印出来的变成了自定义组件的实例
因为ref的current指向的是组件的实例,和函数组件没有实例,所以无法将ref用在函数组件上
将刚才的自定义组件ChildComponent改成下面这个函数组件
function ChildComponent(props){
return <div>111</div>
}
控制台会出现报错如下
但是我们还是可以函数组件内部使用ref的
function ChildComponent(props){
let textRef=React.createRef();
function consoleRef(){
console.log(textRef.current);
}
return <div>
<p ref={textRef}>123</p>
<button onClick={consoleRef}>btn</button>
</div>
}
这里能用是理所当然的,这和是函数组件或者是class组件没有关系,ref是指向html元素的。
传递DOM refs
ref指向子组件
通过在子组件上面使用ref属性指向React.createRef()创建的refs来达到将DOM从子组件传递到父组件的目的
render(){
return <div>
<button onClick={this.btnClick}>123</button>
{/* ref指向自定义组件 */}
<ChildComponent ref={this.myRef} />
</div>
}
然而,这种方法并非理想的解决方法,我们最终只能得到子组件的实例,而非DOM,更何况上面也说了,函数组件无法使用该方法。
refs转发
refs转发通过React.forwardRef来创建一个子组件,并获取父组件的ref,将内部的元素中的ref属性指向传递来的ref,达到将DOM传递到父组件的目的
function ChildComponent(props){
let textRef=React.createRef();
function consoleRef(){
console.log(textRef.current);
}
return <div>
{/* 获取子组件中的refs */}
<RefBtn ref={textRef} />
<button onClick={consoleRef}>clickbtn</button>
</div>
}
const RefBtn = React.forwardRef((props,ref)=>{
// ref属性指向父组件传下来的ref
return <button ref={ref}>
refBtn
</button>
})
点击clickBtn,会打印出button的DOM
React.forwardRef生成的组件在react-devtools中被当成一个组件
refs回调
refs回调是另一种设置refs的方法,不同于直接设置值,refs回调是通过传递一个方法,在方法中完成对refs的赋值,与之前不同的是,refs回调可以直接将元素赋值给refs,这样就不用多用一步去调用current属性了
class MyComponent extends React.Component{
constructor(props){
super(props);
// 创建ref
this.myRef=null;
this.setMyRef=element=>{
this.myRef=element;
}
// 绑定方法
this.btnClick=this.btnClick.bind(this);
}
btnClick(){
// 打印ref对应的元素
// 打印的不再是current属性
if(this.myRef)console.log(this.myRef);
}
componentDidMount(){
this.btnClick()
}
render(){
return <div>
<button onClick={this.btnClick}>123</button>
<div ref={this.setMyRef}>div</div>
</div>
}
}