Loading... *阅读时间约: 14 分钟。* --- 在 React 程序中,我们经常需要在组件之间共享一些状态或引用。灵活使用 `useRef` 可以帮我们实现之一目的。通常来说,`useRef` 可用于在组件之间共享数据,还可以存储 DOM 节点的引用。 在本文中,我们将深入探讨 `useRef` 钩子函数的使用方法,比如: 1. 如何使用 `useRef` 在组件之间共享数据,以及与传统的全局变量和 `Redux` 状态管理的对比; 2. 使用 `useRef` 存储 DOM 元素引用的方法,以及在什么情况下使用 `useRef` 比 `React.createRef` 更加方便; 3. 我们还会探讨 `useRef` 的另一种用法,即在函数式组件中保存变量的值。 通过本文,我们会更深入地理解 `useRef` 钩子函数,并了解在实际的项目中如何应用 `useRef` 来提高代码的可读性和可维护性。 ## `useRef` 的基础用法 `useRef` 是 React 中的一个钩子函数,用于创建一个可变的引用。它的定义方式如下: ```jsx const refContainer = useRef(initialValue); ``` 其中,`refContainer` 是创建的引用容器,可以在整个组件中使用;`initialValue` 是可选的,它是 `refContainer` 的初始值。 `useRef` 返回的是一个包含 `current` 属性的对象,该属性可以存储任何值。我们可以使用 `refContainer.current` 获取或修改 `current` 属性的值。需要注意的是,当我们修改 `current` 属性的值时,不会触发组件的重新渲染。 `下面是 useRef 的一个使用示例:` ```jsx 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` 在两个组件间传递数据。假设有一个父组件和一个子组件,父组件有一个输入框,用户输入数据后,点击按钮传递数据给子组件展示。 ```jsx /** * @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` 来保存全局状态的示例代码: ```jsx 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`** 来实现每秒钟更新一个计数器的操作: ```jsx 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` 来提高代码的可读性和可维护性。 如果这篇文章对你有所帮助,不用赞赏, **点赞** 、 **在看** 、**评论**走起~ 你的鼓励就是对我最大支持,小的在此拜谢各位读者大大 😘 Last modification:April 12, 2023 © Allow specification reprint Support Appreciate the author AliPayWeChat Like 2 如果觉得我的文章对你有用,请随意赞赏