useRef
useRef
是一个 React Hook,它能帮助引用一个不需要渲染的值。
- 使用用 ref 引用一个值
- 通过 ref 操作 DOM
- 避免重复创建 ref 的内容
const ref = useRef(initialValue);
参数
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 对象都将会创建两次,但是其中一个版本将被丢弃。如果使用的是组件纯函数(也应当如此),那么这不会影响其行为。
使用方法
1. 通过 ref 操作 DOM
// 访问 dom 元素
import { useRef, useEffect } from "react";
function TextInput() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus(); // 组件挂载时聚焦输入框
}, []);
return <input ref={inputRef} type='text' />;
}
// 组件暴露ref
import { useRef } from "react";
function MyInput({ ref }) {
return <input ref={ref} />;
}
export default function Form() {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return (
<>
<MyInput ref={inputRef} />
<button onClick={handleClick}>聚焦输入框</button>
</>
);
}
2. 使用 ref 引用一个值
import { useRef } from "react";
function Stopwatch() {
const intervalRef = useRef(0);
// ...
function handleStartClick() {
const intervalId = setInterval(() => {
// ...
}, 1000);
intervalRef.current = intervalId;
}
function handleStopClick() {
const intervalId = intervalRef.current;
clearInterval(intervalId);
}
}
** tip:warning 不要在渲染期间写入或者读取 ref.current。**
在 渲染期间 读取或写入 ref 会破坏这些预期行为。
function MyComponent() {
// ...
// 🚩 不要在渲染期间写入 ref
myRef.current = 123;
// ...
// 🚩 不要在渲染期间读取 ref
return <h1>{myOtherRef.current}</h1>;
}
可以在 事件处理程序或者 Effect 中读取和写入 ref。
function MyComponent() {
// ...
useEffect(() => {
// ✅ 可以在 Effect 中读取和写入 ref
myRef.current = 123;
});
// ...
function handleClick() {
// ✅ 可以在事件处理程序中读取和写入 ref
doSomething(myOtherRef.current);
}
// ...
}
3. 避免重复创建 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
是不允许的。然而,在这种情况下是可以的,因为结果总是一样的,而且条件只在初始化时执行,所以是完全可预测的。