useRef 是一个 React Hook,它让你引用一个不需要用于渲染的值。

const ref = useRef(initialValue)

参考

🌐 Reference

useRef(initialValue)

在组件的顶层调用 useRef 来声明一个 ref.

🌐 Call useRef at the top level of your component to declare a ref.

import { useRef } from 'react';

function MyComponent() {
const intervalRef = useRef(0);
const inputRef = useRef(null);
// ...

查看更多示例。

参数

🌐 Parameters

  • initialValue:你希望 ref 对象的 current 属性最初的值。它可以是任何类型的值。这个参数在初次渲染后会被忽略。

返回

🌐 Returns

useRef 返回一个只有单个属性的对象:

  • current:最初,它被设置为你传入的 initialValue。你可以稍后将其设置为其他值。如果你将 ref 对象作为 JSX 节点的 ref 属性传递给 React,React 将设置它的 current 属性。

在接下来的渲染中,useRef 将返回相同的对象。

🌐 On the next renders, useRef will return the same object.

注意事项

🌐 Caveats

  • 你可以修改 ref.current 属性。与状态不同,它是可变的。但是,如果它包含一个用于渲染的对象(例如,你状态的一部分),那么你不应该修改那个对象。
  • 当你更改 ref.current 属性时,React 不会重新渲染你的组件。因为 ref 是一个普通的 JavaScript 对象,React 不知道你何时更改它。
  • 在渲染期间不要写入或读取 ref.current,除非用于初始化。否则会使组件的行为不可预测。
  • 在严格模式下,React 会 调用你的组件函数两次,以便帮助你发现意外的副作用。这是仅在开发环境下的行为,对生产环境没有影响。每个 ref 对象都会被创建两次,但其中一个版本会被丢弃。如果你的组件函数是纯函数(应该是这样),这不会影响行为。

用法

🌐 Usage

使用引用引用一个值

🌐 Referencing a value with a ref

在组件的顶层调用 useRef 来声明一个或多个 refs.

🌐 Call useRef at the top level of your component to declare one or more refs.

import { useRef } from 'react';

function Stopwatch() {
const intervalRef = useRef(0);
// ...

useRef 返回一个 ref 对象 ,其中包含一个初始值为你提供的 初始值 的单个 current 属性

在下次渲染时,useRef 将返回相同的对象。你可以更改它的 current 属性来存储信息,并在稍后读取它。这可能会让你想起 state,但这里有一个重要的区别。

🌐 On the next renders, useRef will return the same object. You can change its current property to store information and read it later. This might remind you of state, but there is an important difference.

更改 ref 不会触发重新渲染。 这意味着 ref 非常适合存储不会影响组件视觉输出的信息。例如,如果你需要存储一个间隔 ID 并在以后检索它,你可以将其放在 ref 中。要更新 ref 内的值,你需要手动更改它的 current 属性:

function handleStartClick() {
const intervalId = setInterval(() => {
// ...
}, 1000);
intervalRef.current = intervalId;
}

稍后,你可以从 ref 中读取该间隔 ID,这样你就可以调用 清除该间隔:

🌐 Later, you can read that interval ID from the ref so that you can call clear that interval:

function handleStopClick() {
const intervalId = intervalRef.current;
clearInterval(intervalId);
}

通过使用引用,你可以确保:

🌐 By using a ref, you ensure that:

  • 你可以在重新渲染之间存储信息(不同于普通变量,它们在每次渲染时都会重置)。
  • 更改它不会触发重新渲染(不同于会触发重新渲染的状态变量)。
  • 这些信息是每个组件副本本地的(不同于外部的变量,它们是共享的)。

更改 ref 不会触发重新渲染,因此 ref 不适合存储你想在屏幕上显示的信息。应该使用 state 来实现这一点。阅读更多关于 useRefuseState 之间选择 的内容。

🌐 Changing a ref does not trigger a re-render, so refs are not appropriate for storing information you want to display on the screen. Use state for that instead. Read more about choosing between useRef and useState.

Examples of referencing a value with useRef

例子 1 of 2:
点击计数器

🌐 Click counter

这个组件使用 ref 来跟踪按钮被点击的次数。请注意,在这里使用 ref 而不是 state 是可以的,因为点击次数只在事件处理程序中被读取和写入。

🌐 This component uses a ref to keep track of how many times the button was clicked. Note that it’s okay to use a ref instead of state here because the click count is only read and written in an event handler.

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 不会触发重新渲染。用于渲染的信息应该是状态(state)而不是其他内容。

🌐 If you show {ref.current} in the JSX, the number won’t update on click. This is because setting ref.current does not trigger a re-render. Information that’s used for rendering should be state instead.

易犯错误

在渲染期间不要写入或读取 ref.current

React 期望你的组件主体表现得像纯函数

🌐 React expects that the body of your component behaves like a pure function:

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

在渲染过程中读取或写入引用会破坏这些预期。

🌐 Reading or writing a ref during rendering breaks these expectations.

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

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

🌐 You can read or write refs from event handlers or effects instead.

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);
}
// ...
}

如果你必须在渲染期间阅读或写入某些内容,请改用状态

🌐 If you have to read or write something during rendering, use state instead.

当你违反这些规则时,你的组件可能仍然可以工作,但我们正在向 React 添加的大多数新功能将依赖于这些预期。阅读更多关于保持你的组件纯粹的信息。

🌐 When you break these rules, your component might still work, but most of the newer features we’re adding to React will rely on these expectations. Read more about keeping your components pure.


使用引用操作 DOM

🌐 Manipulating the DOM with a ref

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

🌐 It’s particularly common to use a ref to manipulate the DOM. React has built-in support for this.

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

import { useRef } from 'react';

function MyComponent() {
const inputRef = useRef(null);
// ...

然后将你的 ref 对象作为 ref 属性传递给你想要操作的 DOM 节点的 JSX:

🌐 Then pass your ref object as the ref attribute to the JSX of the DOM node you want to manipulate:

// ...
return <input ref={inputRef} />;

在 React 创建 DOM 节点并将其显示在屏幕上之后,React 会将你的 ref 对象的 current 属性 设置为该 DOM 节点。现在你可以访问 <input> 的 DOM 节点并调用像 focus() 这样的方法:

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

当节点从屏幕上移除时,React 会将 current 属性重置为 null

🌐 React will set the current property back to null when the node is removed from the screen.

了解更多关于使用 refs 操作 DOM的信息。

🌐 Read more about manipulating the DOM with refs.

Examples of manipulating the DOM with useRef

例子 1 of 4:
聚焦文本输入

🌐 Focusing a text input

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

🌐 In this example, clicking the button will focus the input:

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>
    </>
  );
}


避免重新创建参考内容

🌐 Avoiding recreating the ref contents

React 会保存一次初始引用值,并在下一次渲染时忽略它。

🌐 React saves the initial ref value once and ignores it on the next renders.

function Video() {
const playerRef = useRef(new VideoPlayer());
// ...

尽管 new VideoPlayer() 的结果只用于初始渲染,但你仍然在每次渲染时调用这个函数。如果它创建的是昂贵的对象,这可能会很浪费。

🌐 Although the result of new VideoPlayer() is only used for the initial render, you’re still calling this function on every render. This can be wasteful if it’s creating expensive objects.

要解决它,你可以像这样初始化引用:

🌐 To solve it, you may initialize the ref like this instead:

function Video() {
const playerRef = useRef(null);
if (playerRef.current === null) {
playerRef.current = new VideoPlayer();
}
// ...

通常,在渲染过程中写入或读取 ref.current 是不允许的。然而,在这种情况下是可以的,因为结果总是相同的,并且该条件只在初始化时执行,因此是完全可预测的。

🌐 Normally, writing or reading ref.current during render is not allowed. However, it’s fine in this case because the result is always the same, and the condition only executes during initialization so it’s fully predictable.

深入研究

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

🌐 How to avoid null checks when initializing useRef later

如果你使用类型检查器并且不想总是检查 null,你可以尝试使用如下模式:

🌐 If you use a type checker and don’t want to always check for null, you can try a pattern like this instead:

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()

🌐 Here, the playerRef itself is nullable. However, you should be able to convince your type checker that there is no case in which getPlayer() returns null. Then use getPlayer() in your event handlers.


故障排除

🌐 Troubleshooting

我无法获得自定义组件的引用

🌐 I can’t get a ref to a custom component

如果你尝试像这样向你自己的组件传递 ref

🌐 If you try to pass a ref to your own component like this:

const inputRef = useRef(null);

return <MyInput ref={inputRef} />;

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

🌐 You might get an error in the console:

Console

默认情况下,你自己的组件不会将引用公开给其中的 DOM 节点。

🌐 By default, your own components don’t expose refs to the DOM nodes inside them.

要解决此问题,请找到你想要获取引用的组件:

🌐 To fix this, find the component that you want to get a ref to:

export default function MyInput({ value, onChange }) {
return (
<input
value={value}
onChange={onChange}
/>
);
}

然后将 ref 添加到你的组件接受的 props 列表中,并像这样将 ref 作为 prop 传递给相关的子 内置组件

🌐 And then add ref to the list of props your component accepts and pass ref as a prop to the relevant child built-in component like this:

function MyInput({ value, onChange, ref }) {
return (
<input
value={value}
onChange={onChange}
ref={ref}
/>
);
};

export default MyInput;

然后父组件可以获得对它的引用。

🌐 Then the parent component can get a ref to it.

了解更多关于访问另一个组件的 DOM 节点的信息。

🌐 Read more about accessing another component’s DOM nodes.