likes
comments
collection
share

仅此一文,让你全完掌握React中的useRef钩子函数

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

阅读时间约:14 分钟。


在 React 程序中,我们经常需要在组件之间共享一些状态或引用。灵活使用 useRef 可以帮我们实现之一目的。通常来说,useRef 可用于在组件之间共享数据,还可以存储 DOM 节点的引用。

在本文中,我们将深入探讨 useRef 钩子函数的使用方法,比如:

  1. 如何使用 useRef 在组件之间共享数据,以及与传统的全局变量和 Redux 状态管理的对比;
  2. 使用 useRef 存储 DOM 元素引用的方法,以及在什么情况下使用 useRef 比 React.createRef 更加方便;
  3. 我们还会探讨 useRef 的另一种用法,即在函数式组件中保存变量的值。

通过本文,我们会更深入地理解 useRef 钩子函数,并了解在实际的项目中如何应用 useRef 来提高代码的可读性和可维护性。

useRef 的基础用法

useRef 是 React 中的一个钩子函数,用于创建一个可变的引用。它的定义方式如下:

const refContainer = useRef(initialValue);

其中,refContainer 是创建的引用容器,可以在整个组件中使用;initialValue 是可选的,它是 refContainer 的初始值。

useRef 返回的是一个包含 current 属性的对象,该属性可以存储任何值。我们可以使用 refContainer.current 获取或修改 current 属性的值。需要注意的是,当我们修改 current 属性的值时,不会触发组件的重新渲染。

下面是 useRef 的一个使用示例:

import { useRef } from 'react';

function Component() {
  // 创建一个 ref 对象
  const inputRef = useRef(null);

  // 当按钮被点击时,调用这个函数将 input 元素聚焦
  const handleButtonClick = () => {
    inputRef.current.focus();
  };

  return (
    <div>
      {/* 将 inputRef 对象传递给 input 元素的 ref 属性 */}
      <input type="text" ref={inputRef} />
      {/* 当按钮被点击时调用 handleButtonClick 函数 */}
      <button onClick={handleButtonClick}>Focus Input</button>
    </div>
  );
}

在这个示例中,我们使用 useRef 创建了一个 inputRef 引用容器,并将其传递给 input 元素的 ref 属性。当点击按钮时,我们调用 handleButtonClick 函数,该函数通过 inputRef.current 获取 input 元素,并将焦点聚焦到该元素上。

我们注意到,useRef 与传统的 JavaScript 使用 const 或 let 声明变量的方式不同,在 React 中使用 useRef 可以带来一些不同的体验和优势。

首先,在某些情况下,我们需要在组件的整个生命周期中保持某个值的引用,而这个值又可能会被修改,这时候 useRef 就派上用场了。

其次,通过 useRef 声明的变量更新不会触发组件的重新渲染。这意味着,当我们修改 useRef 的变量时,React 不会将其视为状态的更新,从而避免了不必要的重新渲染。

最后,useRef 的变量在组件的重新渲染过程中保持不变。这意味着,我们可以在组件重新渲染后,仍然可以访问 useRef 的变量。

利用 useRef 实现组件间的通信

useRef 在组件间通信的应用场景主要有 两种 ,分别是:

  1. 传递数据给子组件 :在组件层次结构中传递数据给子组件,以避免数据的多次传递和组件的嵌套。
  2. 保存全局状态 :在多个组件中共享同一变量,以避免状态丢失和混乱。

下面我们通过一个复制输入框中文本的示例代码来演示如何使用 useRef 在两个组件间传递数据。假设有一个父组件和一个子组件,父组件有一个输入框,用户输入数据后,点击按钮传递数据给子组件展示。

/**
 * @description 用户点击按钮后,复制输入框中的文本 
 */

import { useRef, useState } from 'react';

function Parent() {
  // 使用 useState 钩子函数定义一个 inputValue 变量和其更新函数 setInputValue
  const [inputValue, setInputValue] = useState('');
  // 使用 useRef 钩子函数定义一个 inputRef 变量,并初始化为 null
  const inputRef = useRef(null);

  // 点击按钮时,将焦点设置到 input 元素上
  const handleButtonClick = () => {
    inputRef.current.focus();
  };
  // 输入框内容变化时,更新 inputValue 的值
  const handleInputChange = (e) => {
    setInputValue(e.target.value);
  };

  return (
    <div>
      {/* input 元素绑定 inputRef,value 值绑定 inputValue,onChange 事件绑定 handleInputChange 函数 */}
      <input type="text" ref={inputRef} value={inputValue} onChange={handleInputChange} />
      {/* 点击按钮时,执行 handleButtonClick 函数 */}
      <button onClick={handleButtonClick}>Focus Input</button>
      {/* 将 inputRef 传递给子组件 Child */}
      <Child inputRef={inputRef} />
    </div>
  );
}

function Child(props) {
  // 点击按钮时,选中 inputRef 指向的 input 元素,并执行 copy 命令
  const handleButtonClick = () => {
    props.inputRef.current.select();
    document.execCommand('copy');
  };

  return (
    <div>
      {/* 点击按钮时,执行 handleButtonClick 函数 */}
      <button onClick={handleButtonClick}>Copy Text</button>
    </div>
  );
}

上面的示例代码由一个父组件 Parent 和一个子组件 Child 组成。

在 Parent 组件中,定义了一个输入框和一个按钮,还有一个 inputRef 对象,用来引用输入框元素。当用户在输入框中输入内容时,handleInputChange 方法会被触发,将输入框的值更新到状态变量 inputValue 中。当用户点击按钮时,handleButtonClick 方法会被触发,将输入框元素聚焦。

Parent 组件将 inputRef 对象通过 props 传递给了 Child 组件。在 Child 组件中,定义了一个按钮,当用户点击该按钮时,会使用 inputRef.current.select() 方法将父组件中的输入框文本选中,并通过 document.execCommand('copy') 方法将选中的文本复制到剪切板中。

通过使用 inputRef 对象,在父子组件之间实现了数据的传递和操作。

这个简单的示例为我们演示了通过 useRef 实现将数据传递给子组件的方法。这个例子中的 inputRef 主要用于在父组件和子组件之间传递,它并没有被用来存储全局状态。那么,如果我们要在全局保存一个状态该怎么实现呢?答案是在整个应用程序范围内都需要访问和共享状态,而不是在单个组件内使用的状态。

以下是一个使用 useRef 来保存全局状态的示例代码:

import { useRef, useState } from 'react';

function App() {
  const [count, setCount] = useState(0);
  const prevCountRef = useRef();

  // 保存 count 的前一个值
  prevCountRef.current = count;

  const handleButtonClick = () => {
    // 访问 prevCountRef.current 来获取之前的值
    console.log(`Count: ${count}, Previous Count: ${prevCountRef.current}`);
    setCount(count + 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleButtonClick}>Increase Count</button>
    </div>
  );
}

在这个例子中,我们使用了 useState 来声明一个状态变量 count,并且使用 useRef 来保存它的前一个值 prevCount。在每次 count 发生变化时,我们将当前值存储到 prevCountRef.current 中,以便以后可以访问它。在点击按钮时,我们使用 console.log 来记录当前值和前一个值,并将 count 增加 1。通过这个示例,我们可以看到 useRef 如何在组件之间共享状态并保留它们的值。

利用 useRef 实现定时器等操作

有时,我们在 React 程序中需要执行定时器、轮询等操作。通常情况下,我们可以使用 setInterval 或 setTimeout 等 JavaScript 原生方法来实现这些操作。但在使用 React 的过程中,我们需要注意一些细节,例如当组件被卸载时应该清除定时器等操作,否则可能会导致一些未预期的问题。

在这种情况下,useRef 钩子函数就派上用场了。通过使用 useRef ,我们可以在组件之间共享和保存数据,从而实现定时器等操作。

下面这个简单的例子便演示了如何使用 useRef 来实现每秒钟更新一个计数器的操作:

import { useRef, useEffect } from 'react';

function Counter() {
  // 使用 useRef 声明一个 intervalRef 变量,并将其初始值设为 null
  const intervalRef = useRef(null);
  // 使用 useState 声明 count 变量和 setCount 函数,并将初始值设为 0
  const [count, setCount] = useState(0);

  useEffect(() => {
    // 在组件挂载时,使用 setInterval 函数,每隔一秒更新一次 count 的值
    intervalRef.current = setInterval(() => {
      setCount(c => c + 1);
    }, 1000);

    // 在组件卸载时,使用 clearInterval 函数清除定时器
    return () => {
      clearInterval(intervalRef.current);
    };
  }, []);

  // 在页面上渲染当前 count 的值和一个“停止”按钮
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => clearInterval(intervalRef.current)}>Stop</button>
    </div>
  );
}

在这个示例中,我们使用 useRef 创建了一个 intervalRef 引用,并将其初始化为 null 。然后在组件中使用 useEffect 钩子函数来设置一个定时器,每隔一秒钟更新计数器。注意,在 useEffect 的回调函数中,我们将 intervalRef.current 设置为 setInterval 返回的定时器 id,以便在组件卸载时清除定时器。最后,当我们点击页面上的按钮时可以停止定时器。

与其他钩子函数的联合使用

像上面的定时器的例子,useRef 可以和许多其他内置的钩子函数联合使用,比如常见的 useState、useEffect 等。具体和哪些钩子函数联合使用,还要根据具体的应用场景和业务逻辑。再比如我们熟知的 antd 组件库,其中的 Modal 弹框或 Form 表单同样提供了 ref 引用,结合内置的其他钩子函数可以大大提高我们的开发效率。

注意事项和常见误区

通过上面的示例我们不难发现 useRef 的强大和灵活,但是在使用 useRef 时,我么可能会遇到其他问题,比如下面这些:

  1. ref 对象的 current 属性可能为空 在使用 useRef 创建 ref 对象后,应该始终检查 current 属性是否为空。如果 current 属性为空,则意味着 ref 对象未被正确地绑定到组件或 DOM 元素。
  2. ref 对象的 current 属性是可变的 与传统的变量不同,ref 对象的 current 属性是可变的。这意味着您可以在组件中的任何地方更改它,并且这些更改将在整个组件中反映出来。因此,如果多个组件共享同一个 ref 对象,更改该对象可能会影响其他组件。
  3. 不要滥用 useRef 尽管 useRef 可以用于许多场景,但不要滥用它。如果您只需要在渲染之间存储一些值,可以考虑使用局部变量或 useState 钩子。
  4. useRef 并不是解决所有问题的银弹 尽管 useRef 是一个强大的工具,但它并不是解决所有问题的银弹。在使用 useRef 时,应该了解它的局限性,并寻找其他解决方案来解决特定问题。

总结

本文深入探讨了 useRef 钩子函数的使用方法,包括如何在组件之间共享数据,如何存储 DOM 元素的引用,以及在函数式组件中保存变量的值。我们还讨论了 useRef 在组件间通信和全局状态管理的应用方法。最后,我们介绍了如何利用 useRef 实现定时器等操作。

通过本文,我们深入了解了 useRef 钩子函数的使用方法,并了解了在实际的项目中如何应用 useRef 来提高代码的可读性和可维护性。

如果这篇文章对你有所帮助,赶紧双击屏幕呱唧呱唧 👏👏👏