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属性。与 state 不同,它是可变的。不过,如果它持有一个用于渲染的对象(例如,你的一部分 state),那么你不应该修改那个对象。 - 当你改变
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 属性来存储信息,并在之后读取。这可能会让你联想到 state,但二者有一个重要区别。
修改 ref 不会触发重新渲染。 这意味着 ref 非常适合存储那些不会影响组件视觉输出的信息。比如,如果你需要存储一个 interval ID 并在之后取回它,你可以把它放进 ref。要更新 ref 里的值,你需要手动修改它的 current 属性:
function handleStartClick() {
const intervalId = setInterval(() => {
// ...
}, 1000);
intervalRef.current = intervalId;
}之后,你可以从 ref 中读取这个 interval ID,以便调用 清除该计时器:
function handleStopClick() {
const intervalId = intervalRef.current;
clearInterval(intervalId);
}使用 ref,可以确保:
- 你可以在重新渲染之间 存储信息(不同于普通变量,它们会在每次渲染时重置)。
- 修改它 不会触发重新渲染(不同于 state 变量,它们会触发重新渲染)。
- 信息是局部的,属于组件的每个副本(不同于外部变量,它们是共享的)。
修改 ref 不会触发重新渲染,所以 ref 不适合存储你想显示在屏幕上的信息。对此请改用 state。更多内容请阅读 在 useRef 和 useState 之间进行选择。
Example 1 of 2: 点击计数器
这个组件使用 ref 来跟踪按钮被点击了多少次。请注意,这里使用 ref 而不是 state 是可以的,因为点击次数只会在事件处理函数中读取和写入。
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。
使用 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。
更多内容请阅读 使用 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 是不允许的。不过,在这个例子中是可以的,因为结果始终相同,而且这个条件只会在初始化时执行,所以它是完全可预测的。
Deep Dive
如果你使用类型检查器,并且不想总是检查 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}
/>
);
}然后将 ref 添加到组件接受的 props 列表中,并像这样把 ref 作为 prop 传递给相关的子 内置组件:
function MyInput({ value, onChange, ref }) {
return (
<input
value={value}
onChange={onChange}
ref={ref}
/>
);
};
export default MyInput;这样父组件就可以获取到它的 ref 了。
阅读更多关于访问另一个组件的 DOM 节点。