useRef
是一个 React Hook,允许你引用一个不需要用于渲染的值。
const ref = useRef(initialValue)
参考
useRef(initialValue)
在组件的顶层调用 useRef
来声明一个 ref。
import { useRef } from 'react';
function MyComponent() {
const intervalRef = useRef(0);
const inputRef = useRef(null);
// ...
参数
initialValue
:你希望 ref 对象的current
属性最初具有的值。它可以是任何类型的值。此参数在初始渲染后会被忽略。
返回值
useRef
返回一个只有一个属性的对象
current
:最初,它被设置为你传递的initialValue
。你之后可以将其设置为其他值。如果你将 ref 对象作为 JSX 节点的ref
属性传递给 React,React 将设置其current
属性。
在下一次渲染中,useRef
将返回相同对象。
注意事项
- 你可以修改
ref.current
属性。与状态不同,它是可变的。但是,如果它包含一个用于渲染的对象(例如,你状态的一部分),那么你不应该修改该对象。 - 当你更改
ref.current
属性时,React 不会重新渲染你的组件。React 不知道你何时更改它,因为 ref 是一个普通的 JavaScript 对象。 - 除了 初始化 之外,不要在渲染期间写入或读取
ref.current
。这会使你的组件行为不可预测。 - 在严格模式下,React 将调用你的组件函数两次以 帮助你查找意外的不纯性。 这是仅限开发环境的行为,不会影响生产环境。每个 ref 对象将被创建两次,但其中一个版本将被丢弃。如果你的组件函数是纯净的(它应该是),则这不应该影响行为。
使用方法
使用 ref 引用值
在组件的顶层调用 useRef
来声明一个或多个 refs。
import { useRef } from 'react';
function Stopwatch() {
const intervalRef = useRef(0);
// ...
useRef
返回一个带有单个 ref 对象 的 current
属性,其初始值设置为 您提供的初始值。
在后续渲染中,useRef
将返回相同的对象。您可以更改其 current
属性来存储信息并在以后读取它。这可能让您想起 状态,但存在重要区别。
更改 ref 不会触发重新渲染。这意味着 ref 非常适合存储不会影响组件视觉输出的信息。例如,如果您需要存储一个 间隔 ID 并在以后检索它,您可以将其放入 ref 中。要更新 ref 内的值,您需要手动更改其 current
属性
function handleStartClick() {
const intervalId = setInterval(() => {
// ...
}, 1000);
intervalRef.current = intervalId;
}
稍后,您可以从 ref 中读取该间隔 ID,以便您可以调用 清除该间隔
function handleStopClick() {
const intervalId = intervalRef.current;
clearInterval(intervalId);
}
通过使用 ref,您可以确保:
- 您可以在重新渲染之间存储信息(与常规变量不同,常规变量在每次渲染时都会重置)。
- 更改它不会触发重新渲染(与状态变量不同,状态变量会触发重新渲染)。
- 该信息对于您的每个组件副本都是本地的(与外部共享的变量不同)。
更改 ref 不会触发重新渲染,因此 ref 不适合存储您想要在屏幕上显示的信息。改为使用状态。阅读有关 选择 useRef
和 useState
的更多信息。
示例 1的 2: 点击计数器
此组件使用 ref 来跟踪按钮被点击的次数。请注意,此处可以使用 ref 代替状态,因为点击次数仅在事件处理程序中读取和写入。
import { useRef } from 'react'; export default function Counter() { let ref = useRef(0); function handleClick() { ref.current = ref.current + 1; alert('You clicked ' + ref.current + ' times!'); } return ( <button onClick={handleClick}> Click me! </button> ); }
如果您在 JSX 中显示 {ref.current}
,则数字在点击时不会更新。这是因为设置 ref.current
不会触发重新渲染。用于渲染的信息应该是状态。
使用 ref 操作 DOM
使用 ref 操作 DOM 特别常见。React 内置了对此的支持。
首先,使用 ref 对象 声明一个 初始值 为 null
import { useRef } from 'react';
function MyComponent() {
const inputRef = useRef(null);
// ...
然后将您的 ref 对象作为 ref
属性传递给您要操作的 DOM 节点的 JSX
// ...
return <input ref={inputRef} />;
在 React 创建 DOM 节点并将其放在屏幕上之后,React 将把 ref 对象的 current
属性 设置为该 DOM 节点。现在您可以访问 <input>
的 DOM 节点并调用诸如 focus()
之类的方法
function handleClick() {
inputRef.current.focus();
}
当节点从屏幕中移除时,React 会将current
属性设置回null
。
阅读更多关于使用 refs 操作 DOM。
避免重新创建ref内容
React 只保存初始 ref 值一次,并在后续渲染中忽略它。
function Video() {
const playerRef = useRef(new VideoPlayer());
// ...
尽管new VideoPlayer()
的结果仅用于初始渲染,但您仍然在每次渲染时都调用此函数。如果它正在创建昂贵的对象,这可能会造成浪费。
为了解决这个问题,您可以尝试这样初始化ref:
function Video() {
const playerRef = useRef(null);
if (playerRef.current === null) {
playerRef.current = new VideoPlayer();
}
// ...
通常,在渲染过程中写入或读取ref.current
是不允许的。但是,在这种情况下是可以的,因为结果始终相同,并且条件仅在初始化期间执行,因此它是完全可预测的。
深入探讨
如果您使用类型检查器并且不想总是检查null
,您可以尝试使用以下模式:
function Video() {
const playerRef = useRef(null);
function getPlayer() {
if (playerRef.current !== null) {
return playerRef.current;
}
const player = new VideoPlayer();
playerRef.current = player;
return player;
}
// ...
这里,playerRef
本身是可空的。但是,您应该能够使类型检查器相信没有getPlayer()
返回null
的情况。然后在您的事件处理程序中使用getPlayer()
。
故障排除
我无法获得自定义组件的ref
如果您尝试像这样将ref
传递给您自己的组件:
const inputRef = useRef(null);
return <MyInput ref={inputRef} />;
您可能会在控制台中收到错误。
默认情况下,您自己的组件不会将其内部的 DOM 节点暴露为 refs。
要解决此问题,请找到您想要获取其 ref 的组件:
export default function MyInput({ value, onChange }) {
return (
<input
value={value}
onChange={onChange}
/>
);
}
然后将其包装在forwardRef
中,如下所示:
import { forwardRef } from 'react';
const MyInput = forwardRef(({ value, onChange }, ref) => {
return (
<input
value={value}
onChange={onChange}
ref={ref}
/>
);
});
export default MyInput;
然后父组件可以获取其 ref。
阅读更多关于访问另一个组件的 DOM 节点。