React ref的使用
ref
可以作用于普通元素,也可以作用于class
组件上,不能作用于函数式组件,因为函数式组件没有this
实例对象,在函数式组件中可以使用useRef
和useImperativeHandle
如果作用于组件上,可以直接访问组件的state
,还可以调用组件的方法(自定义方法以及setState
等内置方法)
父组件设置的ref
还可以转发给子组件,以获取子组件内部的元素或者组件
设置ref的3种方法
设置为字符串(已废弃)
<Child ref="child"></Child>
使用React.createRef()
class Parent extends React.Component {
constructor(...props) {
super(...props)
this.childRef = React.createRef()
}
handleClick = () => {
console.log(this.childRef.current) // Child组件实例,可以访问Child上的state和方法
}
render() {
return <>
<p>Parent</p>
<button onClick={this.handleClick}>点击</button>
<Child ref={this.childRef}></Child>
</>
}
}
使用回调函数
React
也支持另一种设置 refs
的方式,称为“回调 refs
”。它能助你更精细地控制何时 refs
被设置和解除。
不同于传递 createRef()
创建的 ref
属性,你会传递一个函数。这个函数中接受 React
组件实例或 HTML DOM
元素作为参数,以使它们能在其他地方被存储和访问。
class Parent extends React.Component {
constructor(...props) {
super(...props)
this.childInput = null;
}
handleClick = () => {
console.log(this.childInput)
}
render() {
return <>
<p>Parent</p>
<button onClick={this.handleClick}>点击</button>
<Child ref={el => this.childInput = el}></Child>
</>
}
}
如果 ref
回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null
,然后第二次会传入参数绑定的DOM
元素或者组件。这是因为在每次渲染时会创建一个新的函数实例,所以 React
清空旧的 ref
并且设置新的。通过将 ref
的回调函数定义成 class
的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。
import React from 'react'
import Child from './Child'
class Parent extends React.Component {
constructor(...props) {
super(...props)
this.childRef = null;
this.state = {
count: 0
}
}
handleClick = () => {
this.setState({
count: this.state.count + 1
})
}
render() {
return <>
<p>Parent</p>
<button onClick={this.handleClick}>点击</button>
<Child ref={el => {console.log(el); this.childRef = el}}></Child>
</>
}
}
export default Parent
ref转发
为什么需要转发?如果我们给一个组件设置ref
,那么这个ref
是该组件实例,那如果我们需要封装一个组件,在外部调用它的时候,给他设置ref
,则直接抛出组件内部的DOM
元素,这就需要用到转发了;
转发的方式有两种,一个是使用React.forwardRef
api,另一个是作为额外属性传递;
注意,不管是以何种方式定义的ref
,转发都会转发到对应的DOM
元素或者组件;
React.forwardRef
forwardRef
接收一个函数作为参数,该函数有两个入参,一个是props
,另一个就是需要传递的ref
,由于React
内部把ref
属性从props中过滤掉了,因此我们需要单独的接受ref
,我们可以指定这个从父组件传过来的ref
具体绑定到哪个元素上;
import React from 'react'
import Child from './Child'
class Parent extends React.Component {
constructor(...props) {
super(...props)
this.childRef = React.createRef()
}
handleClick = () => {
console.log(this.childRef.current) // Child组件的input元素
}
render() {
return <>
<p>Parent</p>
<button onClick={this.handleClick}>点击</button>
<Child ref={this.childRef}></Child>
</>
}
}
export default Parent
import React from 'react'
const Child = React.forwardRef((props, ref) => {
return
<div>
<p>Child</p>
<input ref={ref}></input>
</div>
})
export default Child
可以看到,像上面的写法我们只能返回一个函数式组件,那么要返回一个class
组件,要怎么写呢,此时我们需要额外的包装一层,有点类似高阶组件,实际上高阶组件中传递ref
也是通过下面这种方式的,由于ref
不会通过props
传递给子组件,因此需要一个额外的属性来传递ref
import React from 'react'
class Child extends React.Component {
constructor(props){
super(props)
}
render() {
return <>
<div>
<p>Child</p>
<input ref={this.props.forwardRef}></input>
</div>
</>
}
}
const wrappedChild = React.forwardRef((props, ref) => {
// 注意透传props
return <Child {...props} forwardRef={ref} />
})
export default wrappedChild
作为属性传递
其实上述的例子中已经用到了作为属性传递ref
的特性,由于ref
不会存在于props
中,因此需要额外的通过一个属性进行传递,这里就不举例子了。
额外的属性除了可以传递createRef
的值之外,还可以传递一个回调refs
,作用和createRef
一样,两者并没有什么明显区别
class Parent extends React.Component {
constructor(...props) {
super(...props)
this.childRef = null;
this.state = {
count: 0
}
}
handleClick = () => {
this.setState({
count: this.state.count + 1
})
}
render() {
return <>
<p>Parent</p>
<button onClick={this.handleClick}>点击</button>
<Child forwardRef={el => {console.log(el); this.childRef = el}}></Child>
</>
}
}
export default Parent
class Child extends React.Component {
constructor(props){
super(props)
this.state = {
count: 0
}
}
render() {
return <>
<div>
<p>Child</p>
<p>{this.state.count}</p>
<input ref={this.props.forwardRef}></input>
</div>
</>
}
}
export default Child
displayName
在 DevTools
中显示自定义名称,例如下面的子组件,在DevTools
中会显示为Child ForwardRef
,否则会显示Anonymous ForwardRef
const Child = React.forwardRef((props, ref) => {
return <div>
<p>Child</p>
<input ref={ref}></input>
</div>
})
Child.displayName = 'Child'
export default Child
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 289211569@qq.com