在 React 函数式组件中使用 refs(一)- useRef + 回调 ref 1. 什么是 refs? 2. 在同一个 React 组件中访问 DOM 节点 3. 访问动态添加的 DOM 元素 4. 结论 5. 参考

2025-06-04

在 React 函数式组件中使用 refs(第一部分)- useRef + 回调 ref

1.什么是 ref?

2. 在同一个 React 组件中访问 DOM 节点

3.访问动态添加的DOM元素

4. 结论

5.参考文献

大家好!👋

最近,我一直在研究函数式组件中的 ref,并决定深入研究一下。此外,我还决定开始写作来提升我的知识,因为只有解释清楚了,才能真正理解一些东西。

这就是我写这个系列文章的初衷!它不会是任何 Ref API 的完整指南,而是基于我在学习过程中的理解,对它进行概述,以便将来使用时更有信心。

这是我的第一篇文章,任何反馈都很有价值。希望也能对你有所帮助。

如果您想检查,我还将这些示例的代码放在了github上。

不用多说,我们走吧!

1.什么是 ref?

Refs 只是对任何事物的引用,例如 DOM 节点、Javascript 值等。为了在功能组件中创建 ref,我们使用钩子useRef(),它返回一个可变对象,该对象的.current属性设置为我们传递给钩子的 initialValue。



const ref = useRef(null); // ref => { current: null }


Enter fullscreen mode Exit fullscreen mode

返回的对象将在组件的整个生命周期内持续存在,即在其所有重新渲染过程中,直到卸载为止。

React 中 refs 主要有两种使用场景

  • 访问底层 DOM 节点或 React 元素
  • 为功能组件创建可变的实例变量

在以下章节和下一篇文章中,我将尝试通过常见场景的示例来介绍一些用例。

2. 在同一个 React 组件中访问 DOM 节点

要在组件中创建对 DOM 节点的引用,我们可以使用useRef()钩子来实现,在大多数情况下这是更简单和最好的方法,或者使用callback ref模式,它可以让你在设置和取消设置 ref 时更好地控制。

让我们在一个有两个按钮的例子中看看它们的比较情况,一个按钮将焦点设置在输入上,另一个按钮记录用户在输入中输入的值。

2.1 useRef()



import React, { useRef } from 'react';

const SimpleRef = () => {
    const inputRef = useRef<HTMLInputElement>(null);

    const onClick = () => {
        console.log('INPUT VALUE: ', inputRef.current?.value);
    }

    const onClickFocus = () => {
        console.log('Focus input');
        inputRef.current?.focus();
    }

    return (
        <div>
            <input ref={inputRef} />
            <button onClick={onClick}>Log value</button>
            <button onClick={onClickFocus}>Focus on input</button>
        </div>
    );
};


Enter fullscreen mode Exit fullscreen mode

由于我们传入的 initialValue 为 null,因此 最初返回一个对象。将其与 及其 ref 属性关联后useRef<HTMLInputElement>(null)我们就可以通过ref 的属性访问 及其属性了。{ current: null }<input>HTMLInputElement.current

这样,当用户单击第一个按钮时,我们会记录用户输入的输入值,当他/她单击第二个按钮时,我们会focus()<input>元素中调用该方法。

由于在这个项目中我使用的是 TypeScript,所以我们必须设置要存储的引用的类型。由于我们将引用放在一个 上<input>,因此我们将其定义为 a HTMLInputElement,并使用可选链来防止在访问引用的属性时出错。

2.2 回调引用

这是 React 支持设置 ref 的另一种方式。与其传递由 创建的 ref 属性useRef(),不如传递一个函数。如文档中所述,该函数接收 React 组件实例或 HTML DOM 元素作为参数,这些参数可以在其他地方存储和访问。

使用回调 ref 创建相同的示例时会有细微的差别。



const SimpleCallbackRef = () => {
    let inputRef: HTMLInputElement | null;

    const onClick = () => {
        console.log('INPUT VALUE: ', inputRef?.value);
    }

    const onFocusClick = () => {
        console.log('Focus input');
        inputRef?.focus();
    }
    console.log('Rendering')
    return (
        <div>
            <input ref={node => { inputRef = node; }} />
            <button onClick={onClick}>Log value</button>
            <button onClick={onFocusClick}>Focus on input</button>
        </div>
    );
};


Enter fullscreen mode Exit fullscreen mode

我们只需使用一个函数来设置 ref 属性,<input>而不是使用 创建的 ref 属性useRef()。该函数接收 DOM 节点并将其赋值给inputRef我们之前声明的 。由于我们没有使用 useRef 创建 ref,因此该inputRef变量存储的是 DOM 元素本身,因此我们无需访问该.current属性,正如您在 onClick 和 onFocusClick 函数中看到的那样。

但是,请注意,我们首先将的类型设置inputRef为 aHTMLInputElement或 null。

这是为什么呢?这是因为使用回调引用时有一个需要注意的地方。正如文档中所述:当它被定义为内联函数时,它会在更新时被调用两次,第一次使用 null,第二次使用 DOM 元素。

因此,Typescript 会警告变量inputRef可能为空(因为节点也可能为空),但像这样输入后,Typescript 就不会报错。
为了解决这个问题,在本例中,我们可以这样做,或者确保仅在节点有效时才将节点赋值给 inputRef:



let inputRef: HTMLInputElement;
// ... the same code
<input ref={node => { 
    console.log('Attaching node: ', node)
    if (node) { // with this we know node is not null or undefined
        inputRef = node;
    }
}} />


Enter fullscreen mode Exit fullscreen mode

此示例仅用于说明如何使用回调引用 (callback ref) 和 useRef 之间的区别。在这种简单的情况下,使用回调引用 (callback ref) 只会增加不必要的工作,因此我建议使用 useRef()。

2.3 回调引用模式警告

仍然讨论这个警告以及如何处理它。直接从文档中获取:

如果将 ref 回调定义为内联函数,它将在更新过程中被调用两次,第一次使用 null,第二次使用 DOM 元素。这是因为每次渲染时都会创建一个新的函数实例,因此 React 需要清除旧的 ref 并设置新的 ref。你可以通过将 ref 回调定义为类上的绑定方法来避免这种情况,但请注意,在大多数情况下这无关紧要。

为了更好地说明此回调引用警告,请参见下面的示例:



import React, { useState } from 'react';

const SimpleCallbackRefRerender = () => {
    let inputRef: HTMLInputElement;
    const [count, setCount] = useState(0);

    const onClick = () => {
        console.log('INPUT VALUE: ', inputRef?.value);
    }

    const onFocusClick = () => {
        console.log('Focus input');
        inputRef?.focus();
    }

    const onRerenderClick = () => {
        console.log('Clicked to re-render');
        setCount(count+1);
    }

    return (
        <div>
            <input ref={node => { 
                console.log('Attached node: ', node)
                if (node) {
                    inputRef = node;
                }
             }} />
            <button onClick={onClick}>Log value</button>
            <button onClick={onFocusClick}>Focus on input</button>
            <button onClick={onRerenderClick}>Re-render count {count}</button>
        </div>
    );
};


Enter fullscreen mode Exit fullscreen mode

替代文本

从日志中可以看到,首次渲染时,callback ref节点HTMLInputElement被传递给了 的 ref 属性<input>。然而,当点击按钮重新渲染时,节点首先为空,然后又变成了实际元素。

发生这种情况的原因是,当组件重新渲染时,它会先卸载,然后 React 会调用回调引用并向其传递 null 来清除旧的引用;当组件再次挂载时,React 会使用 DOM 元素调用回调引用。为了解决这个问题,我们可以在回调引用中检查节点是否为 null/undefined,然后inputRef像我们之前那样将其赋值给变量。

3.访问动态添加的DOM元素

太棒了,我明白了!但为什么我要使用回调引用呢?
好吧,虽然useRef()钩子已经涵盖了引用所需的大多数常见情况,但回调callback ref模式为我们提供了一种更强大的控制方式,可以处理以下情况:子项被动态添加或移除、与父项的生命周期不同,或者需要在引用已挂载时执行任何操作。

让我们考虑一个简单的例子,其中表单的一部分仅当用户单击第一个按钮时才会出现,并且当它发生时,我们希望新显示的输入得到关注。



import React, { useState, useRef } from 'react';

const CallbackRefDynamicChild = () => {
    const inputRef = useRef<HTMLInputElement>(null);
    const [visible, setVisibility] = useState(false);

    const onClick = () => {
        console.log('INPUT VALUE: ', inputRef.current?.value);
        setVisibility(true);
    }

    const onFocusClick = () => {
        console.log('Focus on first input');
        inputRef.current?.focus();
    }

    const callbackRef = (node: HTMLInputElement) => {
        console.log('Attached node: ', node);
        if(node) {
            node.focus();
        }
    }

    console.log('Rendering: ', inputRef);
    return (
        <div>
            <input ref={inputRef} />
            <button onClick={onClick}>Unlock next input</button>
            {visible && (
                <>
                <input ref={callbackRef} />
                <button onClick={onFocusClick}>Focus on first input</button>
                </>
            )}
        </div>
    );
};


Enter fullscreen mode Exit fullscreen mode

由于第二个输入是动态添加的,当状态发生变化并且可见变量设置为 true 时,最好的方法是使用callback ref

当内容发生变化时,它useRef不会通知你。修改.current属性不会导致重新渲染。因此,为了在 React 连接或分离 DOM 节点的 ref 时运行任何效果,我们需要使用回调 ref。

使用callback ref,当第二个输入出现并且 ref 附加到 时<input>callbackRef将使用 调用该函数HTMLInputElement。然后,如果节点不为 null/undefined,我们调用该focus()方法来实现我们想要的效果。

4. 结论

在本系列的第一部分中,我们介绍了在功能组件中使用 refs 的可能方法,用于我们想要在同一组件中访问 DOM 节点的情况。

在接下来的文章中,我们将看到如何使用 refs 访问其他 React 组件以及如何在功能组件中拥有类似实例的变量。

如果您已经读到这里,欢迎您提供任何反馈或评论,指出任何修改建议,我将不胜感激。希望这些内容对您有所帮助 :)

5.参考文献

如果没有其他优秀开发者的文章,这个系列就不可能完成。如果你想了解哪些文章对我的学习有帮助,请点击以下链接:

https://moduscreate.com/blog/everything-you-need-to-know-about-refs-in-react/
https://blog.logrocket.com/how-to-use-react-createref-ea014ad09dba/
https://www.robinwieruch.de/react-ref
https://medium.com/trabe/react-useref-hook-b6c9d39e2022
https://elfi-y.medium.com/react-callback-refs-a-4bd2da317269
https://linguinecode.com/post/how-to-use-react-useref-with-typescript
https://reactjs.org/docs/refs-and-the-dom.html

文章来源:https://dev.to/carlosrafael22/using-refs-in-react-functions-components-part-1-userref-callback-ref-2j5i
PREV
我使用 Chakra-Ui 至今学到的东西 结论
NEXT
10 分钟内建立 P2P 连接 准备 我们的第一个 P2P 连接 它是如何工作的?