React ref的使用

  1. React ref的使用
  2. 设置ref的3种方法
    1. 设置为字符串(已废弃)
    2. 使用React.createRef()
    3. 使用回调函数
  3. ref转发
    1. React.forwardRef
    2. 作为属性传递
  4. displayName

React ref的使用

ref可以作用于普通元素,也可以作用于class组件上,不能作用于函数式组件,因为函数式组件没有this实例对象,在函数式组件中可以使用useRefuseImperativeHandle

如果作用于组件上,可以直接访问组件的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