• React 中ref 的使用(类组件和函数组件)以及forwardRef 与 useImperativeHandle 详解


    前言

    在一个父组件中,我们想要获取到其中的节点元素或者子组件实例,从而直接调用其上面的方法。Class 类组件和函数组件是两种不同的写法。

    1. Class 组件中使用ref

    在 React 的 Class 组件中,我们通过 createRef 创建 ref

    class Parent extends React.Component {
        constructor(props) {
            super(props)
            this.inputRef = React.createRef();
        }
        
        componentDidMount() {
            console.log(this.inputRef)
            this.inputRef.current.focus();
        }
        render() {
            return <input ref={this.inputRef} />
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在上面的代码实例中,我们使用了 createRef 创建了一个 ref,将其挂到了原生DOM元素 input 上面,这时候,我们就可以通过 ref.current 获取到这个DOM元素,并且可以调用上面的方法。

    ref 如果挂载到了一个Class 组件上面,ref.current 获取到的就是这个 Class 组件的实例,也可以访问该 Class组件的方法。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mhBeu0cR-1669790444660)(C:\Users\二三\AppData\Roaming\Typora\typora-user-images\image-20221130102359179.png)]

    就拿上面这个页面来说,下面的【图纸计划】是一个子组件,当我们将 ref 挂载到该子组件中时,对应的父组件就可以通过 this.ref.current 调用该子组件上面的方法,比如下拉框展开的回调、下拉框值变化的回调等等。

    2. 函数式组件

    ref 回调函数 会在组件被挂载之后将组件实例传递给函数,函数组件不同于Class组件,函数组件没有实例。所以在正常情况下,ref 是不能挂载函数组件上的。

    不过,函数组件是可以创建 ref 的,和类组件不同,函数组件使用 useRef .

    import React, { memo, useEffect,  useRef, useState } from 'react';
    const Parent = () => {
        const inputRef = useRef(null)
        input childRef = useRef(null)
        
        return (
            <>
            	<input ref={inputRef} onChange={onChange} />
            	<Child ref={childRef} handSave={handSave} />
            </>
        )
    }
    
    export default memo(Parent)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    注意:这样的写法从语法上来说没问题,但是如果想在函数组件中通过 ref.current 获取 DOM元素或者子组件的实例,是拿不到的,需要其他的写法。

    3. createRef 与 useRef 的区别

    • createRef 只能用于 Class 类组件中,useRef 只能用在函数式组件中。
    • createRef 每次渲染都会返回一个新的引用,useRef 每次都会返回相同的引用。
    • 如果函数式组件中使用 createRef 创建的 ref,其值会随着函数式组件的重新执行而不断初始化

    4. 部分结论

    综上所述,可以总结以下几点:

    • 原生 DOM 元素:ref的current 指向该节点
    • class 类组件:ref的current 指向该组件实例
    • 函数式组件:ref的current 指向 null,因为函数式组件没有实例。

    那么对于函数式组件,我们真的没有办法获取并调用子组件上面的方法了吗?

    答案:怎么可能,我们可以通过使用 forwardRef 来解决这个问题。

    5. forwardRef

    forwardRef (引用传递)是一种通过组件向子组件自动传递 引用 ref 的技术。

    一句话概括:React 使用 forwardRef 完成 ref 的 透传,让函数式组件可以正常获取到子组件上面的方法。

    5.1 代码写法

    import React, { memo, forwardRef,  useRef, useImperativeHandle } from 'react';
    
    const App = () => {
        const childRef = useRef(null)
        
        const getFocus = () => {
             // 调用子组件的方法
            childRef.current.inputFocus()
            // 也可以调用暴露出来的其他值
            childRef.current.setData(20)
        }
        
        return (
            <>
            	<Child ref={childRef}/>
            	<button onClick ={getFocus}>点击获取焦点<button/>
            </>
        )
    }
    
    // 子组件
    
    const Child = forwardRef((props, ref) => {
        const inputRef = useRef(null)
        const [data, setData] = useState('10')
        
        // 使输入框获取焦点的方法
        const inputFocus = () => {
            inputRef.current.ocus()
        }
        // 输入框内容改变回调
        const changeValue = () => {
        	console.log('哈哈哈')
        }
        
        // 将该方法暴露给父组件
        useImperativeHandle(ref, () => ({
            inputFocus,
            changeValue,
            data,
            setData
        }))
        
        return <input  ref={inputRef} onChange={changeValue}>
    });
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    5.2 forwardRef 解析

    forwardRef 可以直接包裹一个函数式组件 ,被包裹的函数式组件会获得被分配给自己的ref(作为第二个参数)。如果直接将 ref 分配给没有被 forwardRef 包裹的函数式组件,React 会在控制台会报错。

    forwardRef 会创建一个 React 组件,这个组件能够将其接受的 ref 属性转发到其组件树下的另一个组件中,也即是透传。

    注意:forWardRef 的参数必须是 function

    6. useImperativeHandle 解析

    直接使用 forwardRef ,无法控制要暴露给父组件的值,所以我们使用 useImperativeHandle 来控制要将哪些东西暴露给父组件。

    注意: useImperativeHandle 要与 forwardRef 一起使用。

    调用方式如上面的代码实例所示。

    6.1 传参

    • 第一个参数:ref:接收从 forwardRef 传递过来的 ref。
    • 第二个参数:createHandle:处理函数,返回值作为暴露给父组件的 ref 对象。
    • 第三个参数:deps:依赖项 deps,依赖项更改形成新的 ref 对象,可不传

    useImperativeHandle 为我们提供了一个类似实例的东西,帮助我们通过 useImperativeHandle 的 第二个参数,将所返回的对象的内容挂载到附件的 ref.current 上

    7. 总结

    父、子组件再使用该 hooks 时步骤如下:

    1. 父组件使用 useRef 创建一个 ref 对象,将这个 ref 对象赋给子组件的 ref 属性。
    2. 子组件使用 forwardRef 包裹自己,允许作为函数组件的自己使用 ref。然后使用 useImperativeHandle 钩子函数,在该钩子函数的第二个函数参数中返回一些状态或方法,这个被返回的状态或方法就可以被父组件访问到。
    3. 父组件使用创建的 ref 对象的 current 属性获取子组件暴露出的状态或方法。

    8. 特殊注意点:

    在 函数式组件中使用 forwardRef useImperativeHandle ,有一个特殊的点需要注意:子组件的传参写法。如下所示:

    // 可以使用这样的写法
        <Child ref={childRef} list={drawingPlanList} editable={!isDisabled}/>
    
    // 但不用采用这样的写法:这样的 写法有问题,具体原因待排查
    
    	const childProps = {
    		ref: {childRef},
    		list: {drawingPlanList},
    		editable: {!isDisabled}
    	}
        < Child {...childProps}/>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
  • 相关阅读:
    C++11新特性(右值引用,万能转发)
    有趣且重要的JS知识合集(17)矩形框交互算法
    Mysql字符集-Mysql进阶(一)
    华为云OBS究竟是什么?
    #机器学习--高等数学基础--第四章:不定积分
    浅谈new
    独立站+TikTok是下个风口吗
    图论——基础概念
    已解决 (org.springframework.beans.factory.BeanCreationException)
    el-tree自定义节点内容
  • 原文地址:https://blog.csdn.net/qq_41131745/article/details/128114965