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 对象作为 ref 属性传递给 React 的 JSX 节点,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 不适合存储您想在屏幕上显示的信息。请改用状态。阅读有关 useRefuseState 之间进行选择 的更多信息。

使用 useRef 引用值的示例

点击计数器示例 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.current

React 期望您的组件的函数体 的行为像一个纯函数

  • 如果输入(props状态上下文)相同,它应该返回完全相同的 JSX。
  • 以不同的顺序或使用不同的参数调用它不应该影响其他调用的结果。

在渲染期间读取或写入 ref 会破坏这些期望。

function MyComponent() {
// ...
// 🚩 Don't write a ref during rendering
myRef.current = 123;
// ...
// 🚩 Don't read a ref during rendering
return <h1>{myOtherRef.current}</h1>;
}

您可以从事件处理程序或副作用中读取或写入 ref。

function MyComponent() {
// ...
useEffect(() => {
// ✅ You can read or write refs in effects
myRef.current = 123;
});
// ...
function handleClick() {
// ✅ You can read or write refs in event handlers
doSomething(myOtherRef.current);
}
// ...
}

如果您必须在渲染期间读取或写入内容,请改用 状态

当您违反这些规则时,您的组件可能仍然可以工作,但我们添加到 React 中的大多数新功能都将依赖于这些期望。阅读有关 保持组件纯净 的更多信息。


使用 ref 操作 DOM

使用 ref 来操作 DOM 是特别常见的。React 对此提供了内置支持。

首先,使用 初始值null 声明一个 ref 对象

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

阅读更多关于 使用 ref 操作 DOM。

使用 useRef 操作 DOM 的示例

点击计数器示例 1(续) 4:
聚焦文本输入框

在此示例中,单击按钮将聚焦输入框

import { useRef } from 'react';

export default function Form() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
  }

  return (
    <>
      <input ref={inputRef} />
      <button onClick={handleClick}>
        Focus the input
      </button>
    </>
  );
}


避免重新创建 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。但是,在这种情况下是可以的,因为结果始终相同,并且条件仅在初始化期间执行,因此是完全可预测的。

深入探讨

如何在稍后初始化 useRef 时避免空检查

如果您使用类型检查器并且不想总是检查 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} />;

您可能会在控制台中收到错误消息

控制台
警告:不能为函数组件提供 ref。尝试访问此 ref 将失败。您是要使用 React.forwardRef() 吗?

默认情况下,您自己的组件不会将其内部的 DOM 节点的 ref 暴露出来。

要解决此问题,请找到您要获取其 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 节点。