likes
comments
collection
share

React 组件内Ref获取 及Hooks 内获取ref内容了解

作者站长头像
站长
· 阅读数 14

官网

这里会介绍类组件与函数组件内使用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)
    }
}

打印结果:

React 组件内Ref获取 及Hooks 内获取ref内容了解

受控组件:基于修改数据/状态,让视图更新,达到需要的效果 「推荐」

非受控组件:基于ref获取DOM元素,我们操作DOM元素,来实现需求和效果「偶尔」

基于ref获取DOM元素的语法【参考上述例子】

  1. 给需要获取的元素设置ref='xxx',后期基于this.refs.xxx去获取相应的DOM元素「不推荐使用:在React.StrictMode模式下会报错」

<h2 ref="titleBox">...</h2>

获取:this.refs.titleBox [已弃用]

  1. 把ref属性值设置为一个函数

ref={x=>this.xxx=x}

  • x是函数的形参:存储的就是当前DOM元素
  • 然后我们获取的DOM元素“x”直接挂在到实例的某个属性上(例如:box2) 获取:this.xxx
  1. 基于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
    }
}

运行结果:

React 组件内Ref获取 及Hooks 内获取ref内容了解

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>;
};

运行结果:

React 组件内Ref获取 及Hooks 内获取ref内容了解

可以看到这里的 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获取,后面二者的使用差异:

  1. React.createRef 也是创建ref对象,即可在类组件中使用,也可以在函数组件中使用
  2. useRef 只能在函数组件中用「所有的ReactHook函数,都只能在函数组件中时候用,在类组件中使用会报错」

forwardRef 函数组件ref转发

基于forwardRef实现ref转发,目的:获取子组件内部的某个元素、获取函数组件内部内容

用法:

  1. 使用 forwardRef()并传入参数 props、ref
  2. 使用后,对组件内的内容使用  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

运行结果:

React 组件内Ref获取 及Hooks 内获取ref内容了解

这里对函数组件设置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转发的同时,获取函数子组件内部的状态或者方法,如下图所示:

React 组件内Ref获取 及Hooks 内获取ref内容了解

上面是函数组件获取子元素需要给父组件暴露的相关元素、状态及方法等,那类组件的话,在给组件设置ref时,类组件内的相关属性及方法等都可以获取到。

备注:图片上的DOM曼珠沙华也是我哦~