React 组件内Ref获取 及Hooks 内获取ref内容了解
这里会介绍类组件与函数组件内使用ref的内容 现在着重使用的是函数组件,所以先看类组件相关的内容
类组件
标签 ref设置
例子:
import React, { Component } from 'react'
export default class Demo3 extends Component {
title3 = React.createRef()
render() {
return (
<div>
<h2 ref="title1">标题1</h2>
<h2 ref={x => this.title2 = x}>标题2</h2>
<h2 ref={this.title3}>标题3</h2>
</div>
)
}
componentDidMount() {
// 第一次渲染完毕 【虚拟DOM已经变成真实DOM】:这时可以获取需要操作的元素
console.log("title1", this.refs.title1);
console.log("title2", this.title2)
console.log("title3", this.title3.current)
}
}
打印结果:
受控组件:基于修改数据/状态,让视图更新,达到需要的效果 「推荐」
非受控组件:基于ref获取DOM元素,我们操作DOM元素,来实现需求和效果「偶尔」
基于ref获取DOM元素的语法【参考上述例子】
- 给需要获取的元素设置ref='xxx',后期基于this.refs.xxx去获取相应的DOM元素「不推荐使用:在React.StrictMode模式下会报错」
<h2 ref="titleBox">...</h2>
获取:this.refs.titleBox [已弃用]
- 把ref属性值设置为一个函数
ref={x=>this.xxx=x}
- x是函数的形参:存储的就是当前DOM元素
- 然后我们获取的DOM元素“x”直接挂在到实例的某个属性上(例如:box2) 获取:this.xxx
- 基于React.createRef()方法创建一个REF对象 this.xxx=React.createRef(); //=> this.xxx={current:null} ref={REF对象(this.xxx)} 获取:this.xxx.current
原理:在render渲染的时候,会获取virtualDOM的ref属性
- 如果属性值是一个字符串,则会给this.refs增加这样的一个成员,成员值就是当前的DOM元素
- 如果属性值是一个函数,则会把函数执行,把当前DOM元素传递给这个函数「x->DOM元素」,而在函数执行的内部,我们一般都会把DOM元素直接挂在到实例的某个属性上
- 如果属性值是一个REF对象,则会把DOM元素赋值给对象的current属性
组件 ref设置
import React, { Component } from 'react'
class Child1 extends Component {
render() {
return (
<div>
Child1
{/* 获取类组件内的内容 */}
<h3 ref={x => this.titleBox = x}></h3>
</div>
)
}
}
function Child2() {
return (
<div>Child2</div>
)
}
export default class Demo_ref extends Component {
render() {
return (
<div>
<Child1 ref={x => this.child1 = x} />
<Child2 ref={x => this.child2 = x} />
</div>
)
}
componentDidMount() {
console.log("child1", this.child1) // 存储 子组件的实例对象
console.log("child2", this.child2) // 存储 子组件内部元素,例如:button
}
}
运行结果:
Child1内获取的是组件的实例,基于实例,可以调用子组件内部,挂载到实例上的东西[state/props/refs/组件内部元素定义的titleBox]
这里发现,直接给函数组件设置 ref={x => this.child2 = x} 获取的值是undefined,
通过上述方法发现,函数组件获取不到ref,需要借助 forwardRef【在函数组件内详细介绍】
useRef :使用ref获取DOM
useRef
是一个 React Hook,它能让你引用一个不需要渲染的值。
const ref = useRef(initialValue)
参数:initialValue
:ref 对象的 current
属性的初始值。可以是任意类型的值。这个参数会首次渲染后被忽略。
返回值:
useRef 返回一个只有一个属性的对象: current 「它被设置为你传递的 initialValue。之后你可以把它设置为其他值。如果你把 ref 对象作为一个 JSX 节点的 ref 属性传递给 React,React 将为它设置 current 属性」。
在后续的渲染中,useRef 将返回同一个对象
使用: 1.创建 ref对象 let box = useRef(null); 2.ref赋值: ref={box} 3.使用 box.current
const Demo = function Demo() {
let [num, setNum] = useState(0);
let box = useRef(null);
useEffect(() => {
console.log(box.current);
}, []);
return <div className="demo">
<span className="num" ref={box}>{num}</span>
<Button type="primary" size="small"
onClick={() => {
setNum(num + 1);
}}>
新增
</Button>
</div>;
};
运行结果:
可以看到这里的 box.current 结果为一个在页面加载下的一个初始DOM节点
React.createRef()与useRef()区别
let prev1,
prev2;
const Demo = function Demo() {
let [num, setNum] = useState(0);
let box1 = useRef(null),
box2 = React.createRef();
if (!prev1) {
// 第一次DEMO执行,把第一次创建的REF对象赋值给变量
prev1 = box1;
prev2 = box2;
} else {
// 第二次DEMO执行,我们验证一下,新创建的REF对象,和之前第一次创建的REF对象,是否一致?
console.log(prev1 === box1); //true useRef再每一次组件更新的时候(函数重新执行),再次执行useRef方法的时候,不会创建新的REF对象了,获取到的还是第一次创建的那个REF对象!!
console.log(prev2 === box2); //false createRef在每一次组件更新的时候,都会创建一个全新的REF对象出来,比较浪费性能!!
}
useEffect(() => {
console.log(box1.current);
console.log(box2.current);
}, []);
return <div className="demo">
<span className="num" ref={box1}>{num}</span>
<span className="num" ref={box2}>哈哈哈</span>
<Button type="primary" size="small"
onClick={() => {
setNum(num + 1);
}}>
新增
</Button>
</div>;
};
总结:在类组件中,创建REF对象,我们基于 React.createRef 处理;但是在函数组件中,为了保证性能,我们应该使用专属的 useRef 处理!!
函数组件 ref设置
1.函数方式
ref={x => box = x}
const Demo = function Demo() {
let [num, setNum] = useState(0);
/!* 基于“ref={函数}”的方式,可以把创建的DOM元素(或者子组件的实例)赋值给box变量「不推荐」 *!/
let box;
useEffect(() => {
console.log(box); // <span class="num">0</span>
}, []);
return <div className="demo">
<span className="num" ref={x => box = x}>{num}</span>
<Button type="primary" size="small"
onClick={() => {
setNum(num + 1);
}}>
新增
</Button>
</div>;
};
这个方法和上方元素标签设置ref内的title2类似,因为函数组件内没有this,所以这里直接设置变量使用。
没有this,声明了一个变量box ,让ref={x=>box=x},x是获取DOM元素的实例,将x直接赋给变量box,在第一次渲染完成后,可以通过这个变量拿到DOM元素或者实例。
2.React.createRef()
React.createRef()创建ref对象,获取元素
// 设置
let box = React.createRef();
useEffect(() => {
console.log(box.current);
}, []);
// 使用
<span className="num" ref={box}>{num}</span>
3.useRef()
//设置
let box = useRef(null);
useEffect(() => {
console.log(box.current);
}, []);
//使用
<span className="num" ref={box}>{num}</span>
上述三种方法,第一种是基于函数获取,后面两张都是创建ref获取,后面二者的使用差异:
- React.createRef 也是创建ref对象,即可在类组件中使用,也可以在函数组件中使用
- useRef 只能在函数组件中用「所有的ReactHook函数,都只能在函数组件中时候用,在类组件中使用会报错」
forwardRef 函数组件ref转发
基于forwardRef实现ref转发,目的:获取子组件内部的某个元素、获取函数组件内部内容
用法:
- 使用 forwardRef()并传入参数 props、ref
- 使用后,对组件内的内容使用 ref={ref}
const Child = forwardRef((props, ref) => {
return xxxx // 需要暴漏给父组件的ref元素
})
例子:
import React, { useEffect, useRef, forwardRef } from 'react'
const Child = forwardRef((props, ref) => {
console.log("ref", ref); // 调用组件后,传递过来的值 null
// 调用Child2的时候,设置的ref属性值函数
return (
<div>Child
<button ref={ref}>点击</button>
</div>
)
})
function Demo() {
let childRef = useRef(null)
useEffect(() => {
console.log("childRef", childRef.current)
}, [])
return (
<div>
<Child ref={childRef} />
</div>
)
}
export default Demo
运行结果:
这里对函数组件设置ref没有报错,而且获取到了函数组件内部的元素,这个就和 类组件组件ref设置 内的Child2设置ref一致了,可以采用这种方式
useImperativeHandle 【需配合forwardRef使用】
useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值,应当与 forwardRef 一起使用,实现ref转发 ! 用于获取 获取函数子组件内部元素及状态等
const Child = forwardRef((props, ref) => {
useImperativeHandle(ref, () => {
// 在这里返回的内容,都可以被父组件的REF对象获取到
return {
// 需要暴露给父组件的状态方法等
};
});
return xxx
})
forwardRef使父组件获取到了子组件的内部元素,但是函数子组件内部方法及状态的更新,父组件获取需要通过 useImperativeHandle
例子:
import React, { useState, useEffect, useRef, forwardRef, useImperativeHandle } from 'react'
const Child = forwardRef((props, ref) => {
console.log("ref", ref); // 传入的值 null
let [text, setText] = useState('测试useImperativeHandle');
const submit = () => { };
useImperativeHandle(ref, () => {
// 在这里返回的内容,都可以被父组件的REF对象获取到
return {
text,
submit
};
});
return (
<div>Child
<button ref={ref}>点击</button>
</div>
)
})
function Demo() {
let childRef = useRef(null)
useEffect(() => {
// 在父组件内可以获取到子组件Child相关元素及属性方法等
console.log("childRef", childRef.current)
}, [])
return (
<div>
<Child ref={childRef} />
</div>
)
}
export default Demo
可以看到函数子组件内元素获取使用forwardRef,useImperativeHandle 基于forwardRef实现ref转发的同时,获取函数子组件内部的状态或者方法,如下图所示:
上面是函数组件获取子元素需要给父组件暴露的相关元素、状态及方法等,那类组件的话,在给组件设置ref时,类组件内的相关属性及方法等都可以获取到。
备注:图片上的DOM曼珠沙华也是我哦~
转载自:https://juejin.cn/post/7246665046256943160