v19.2Latest

使用引用操作值

当你希望一个组件“记住”某些信息,但又不想该信息触发新的渲染时,你可以使用引用

你将学习
  • 如何为你的组件添加引用
  • 如何更新引用的值
  • 引用与状态有何不同
  • 如何安全地使用引用

为你的组件添加引用

你可以通过从 React 导入useRef钩子来为你的组件添加引用:

在你的组件内部,调用useRef钩子并将你想要引用的初始值作为唯一参数传入。例如,下面是一个指向值0的引用:

useRef返回一个类似这样的对象:

一支写有'current'的箭头被塞进一个写有'ref'的口袋里。

插画作者:Rachel Lee Nabors

你可以通过ref.current属性访问该引用的当前值。这个值是可变的,意味着你可以读取和写入它。它就像是组件的秘密口袋,React 不会追踪它。(这就是为什么它是 React 单向数据流的“逃生舱”——下面会详细说明!)

一个按钮会在每次点击时递增ref.current

该引用指向一个数字,但是,与状态一样,你可以指向任何东西:字符串、对象,甚至函数。与状态不同,引用是一个带有current属性的普通 JavaScript 对象,你可以读取和修改该属性。

请注意,组件不会随着每次递增而重新渲染。与状态一样,React 会在重新渲染之间保留引用。然而,设置状态会重新渲染组件。更改引用则不会!

示例:构建一个秒表

你可以在单个组件中组合使用引用和状态。例如,让我们制作一个秒表,用户可以通过按下按钮来启动或停止。为了显示用户按下“启动”后经过了多少时间,你需要跟踪用户何时按下了启动按钮以及当前时间是什么。此信息用于渲染,因此你将把它保存在状态中:

当用户按下“启动”时,你将使用setInterval以每 10 毫秒更新一次时间:

当按下“停止”按钮时,你需要取消现有的时间间隔,以便其停止更新now状态变量。你可以通过调用clearInterval来实现,但你需要提供之前用户按下启动时setInterval调用所返回的时间间隔 ID。你需要把时间间隔 ID 保存在某个地方。由于时间间隔 ID 不用于渲染,你可以将其保存在一个引用中:

当一条信息用于渲染时,将其保存在状态中。当一条信息仅被事件处理器需要,并且更改它不需要重新渲染时,使用引用可能更高效。

引用与状态的区别

也许你认为引用似乎比状态更“宽松”——例如,你可以直接改变它们,而不必总是使用状态设置函数。但在大多数情况下,你会想要使用状态。引用是你不会经常需要的“逃生舱口”。以下是状态和引用的比较:

引用状态
useRef(initialValue) 返回 { current: initialValue }useState(initialValue) 返回状态变量的当前值和一个状态设置函数([value, setValue]
更改时不会触发重新渲染。更改时会触发重新渲染。
可变的——你可以在渲染过程之外修改和更新current 的值。“不可变的”——你必须使用状态设置函数来修改状态变量以排队进行重新渲染。
你不应该在渲染期间读取(或写入)current 的值。你可以随时读取状态。但是,每次渲染都有其自己的状态快照,该快照不会改变。

这是一个用状态实现的计数器按钮:

因为 count 值会显示,所以为其使用状态值是合理的。当使用 setCount()设置计数器的值时,React 会重新渲染组件,屏幕也会更新以反映新的计数。

如果你尝试用引用实现这个功能,React 将永远不会重新渲染组件,因此你永远看不到计数变化!看看点击这个按钮如何不会更新其文本

这就是为什么在渲染期间读取ref.current会导致不可靠的代码。如果你需要这样做,请改用状态。

Deep Dive
useRef 内部是如何工作的?

何时使用 ref

通常,当你的组件需要“跳出”React 并与外部 API(通常是不影响组件外观的浏览器 API)通信时,你会使用 ref。以下是少数几种此类情况:

如果你的组件需要存储某个值,但它不影响渲染逻辑,请选择 ref。

使用 ref 的最佳实践

遵循这些原则将使你的组件行为更可预测:

  • 将 ref 视为逃生舱口。ref 在你与外部系统或浏览器 API 交互时很有用。如果你的大部分应用逻辑和数据流都依赖于 ref,你可能需要重新考虑你的方法。
  • 不要在渲染过程中读取或写入ref.current如果在渲染过程中需要某些信息,请使用state。由于 React 不知道ref.current 何时发生变化,即使在渲染时读取它也会让你的组件行为难以预测。(唯一的例外是像 if (!ref.current) ref.current = new Thing()这样的代码,它只在首次渲染期间设置 ref 一次。)

React state 的限制不适用于 ref。例如,state 就像一个每次渲染的快照,并且不会同步更新。但是,当你修改 ref 的当前值时,它会立即改变:

这是因为 ref 本身是一个普通的 JavaScript 对象,因此它的行为也像一个普通对象。

当你使用 ref 时,也不必担心避免可变性。只要被你修改的对象不用于渲染,React 就不关心你对 ref 或其内容做了什么。

Ref 与 DOM

你可以将 ref 指向任何值。然而,ref 最常见的用例是访问 DOM 元素。例如,如果你想以编程方式聚焦一个输入框,这就很方便。当你将一个 ref 传递给 JSX 中的ref 属性时,例如 <div ref={myRef}>,React 会将对应的 DOM 元素放入myRef.current。一旦该元素从 DOM 中移除,React 会将myRef.current更新为null。你可以在 使用 Ref 操作 DOM

总结

  • Ref 是一个用于持有不用于渲染的值的“逃生舱”。您不会经常需要它们。
  • Ref 是一个普通的 JavaScript 对象,具有一个名为current的属性,您可以读取或设置它。
  • 您可以通过调用 useRefHook 来让 React 给您一个 ref。
  • 与 state 类似,ref 允许您在组件的重新渲染之间保留信息。
  • 与 state 不同,设置 ref 的current 值不会触发重新渲染。
  • 不要在渲染期间读取或写入ref.current。这会使您的组件难以预测。

尝试一些挑战

Challenge 1 of 4:修复一个损坏的聊天输入 #

输入一条消息并点击“发送”。您会注意到,在您看到“已发送!”提醒之前会有三秒延迟。在此延迟期间,您可以看到一个“撤销”按钮。点击它。这个“撤销”按钮本应阻止“已发送!”消息的出现。它通过在 clearTimeout 中保存的超时ID来实现这一点。然而,即使在点击“撤销”后,“已发送!”消息仍然会出现。请找出它不起作用的原因,并修复它。


使用 Refs 引用值 | React Learn - Reflow Hub