阅读时间约: 14 分钟。
在 React 程序中,我们经常需要在组件之间共享一些状态或引用。灵活使用 useRef
可以帮我们实现之一目的。通常来说,useRef
可用于在组件之间共享数据,还可以存储 DOM 节点的引用。
在本文中,我们将深入探讨 useRef
钩子函数的使用方法,比如:
- 如何使用
useRef
在组件之间共享数据,以及与传统的全局变量和Redux
状态管理的对比; - 使用
useRef
存储 DOM 元素引用的方法,以及在什么情况下使用useRef
比React.createRef
更加方便; - 我们还会探讨
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
在组件间通信的应用场景主要有 两种 ,分别是:
- 传递数据给子组件 :在组件层次结构中传递数据给子组件,以避免数据的多次传递和组件的嵌套。
- 保存全局状态 :在多个组件中共享同一变量,以避免状态丢失和混乱。
下面我们通过一个复制输入框中文本的示例代码来演示如何使用 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
时,我么可能会遇到其他问题,比如下面这些:
ref
对象的current
属性可能为空
在使用useRef
创建ref
对象后,应该始终检查current
属性是否为空。如果current
属性为空,则意味着ref
对象未被正确地绑定到组件或 DOM 元素。ref
对象的current
属性是可变的
与传统的变量不同,ref
对象的current
属性是可变的。这意味着您可以在组件中的任何地方更改它,并且这些更改将在整个组件中反映出来。因此,如果多个组件共享同一个ref
对象,更改该对象可能会影响其他组件。- 不要滥用 useRef
尽管useRef
可以用于许多场景,但不要滥用它。如果您只需要在渲染之间存储一些值,可以考虑使用局部变量或useState
钩子。 useRef
并不是解决所有问题的银弹
尽管useRef
是一个强大的工具,但它并不是解决所有问题的银弹。在使用useRef
时,应该了解它的局限性,并寻找其他解决方案来解决特定问题。
总结
本文深入探讨了 useRef
钩子函数的使用方法,包括如何在组件之间共享数据,如何存储 DOM 元素的引用,以及在函数式组件中保存变量的值。我们还讨论了 useRef
在组件间通信和全局状态管理的应用方法。最后,我们介绍了如何利用 useRef
实现定时器等操作。
通过本文,我们深入了解了 useRef
钩子函数的使用方法,并了解了在实际的项目中如何应用 useRef
来提高代码的可读性和可维护性。
如果这篇文章对你有所帮助,不用赞赏, 点赞 、 在看 、评论走起~ 你的鼓励就是对我最大支持,小的在此拜谢各位读者大大 😘