React useRef
Hook: 全面解析
useRef
是 React 中用于创建持久化可变引用的核心 Hook,它解决了函数组件中无法直接访问 DOM 元素和保存可变值的问题。
核心特性
- 跨渲染持久化:返回的对象在组件整个生命周期中保持不变
- 变更不会触发重渲染 :修改
.current
属性不会导致组件更新 - 直接访问 DOM 节点:最常用的场景
- 保存任意可变值:类似类组件的实例属性
基础语法
javascript
const refContainer = useRef(initialValue);
initialValue
:初始值(通常为null
)refContainer.current
:访问/修改引用值
主要使用场景
1. 访问 DOM 元素(最常见用法)
jsx
function TextInput() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus(); // 直接操作DOM
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>聚焦输入框</button>
</div>
);
}
2. 存储可变值(不触发重渲染)
jsx
function Timer() {
const [count, setCount] = useState(0);
const intervalRef = useRef(); // 存储定时器ID
useEffect(() => {
intervalRef.current = setInterval(() => {
setCount(c => c + 1);
}, 1000);
return () => clearInterval(intervalRef.current);
}, []);
return <div>计时: {count} 秒</div>;
}
3. 保存上一次状态
jsx
function Counter() {
const [count, setCount] = useState(0);
const prevCountRef = useRef();
useEffect(() => {
prevCountRef.current = count; // 在渲染后更新
});
return (
<div>
<p>当前值: {count}</p>
<p>上一次值: {prevCountRef.current ?? '无'}</p>
<button onClick={() => setCount(c => c + 1)}>增加</button>
</div>
);
}
4. 存储复杂计算结果(跳过重复计算)
jsx
function ExpensiveComponent() {
const [value, setValue] = useState(0);
const computedValueRef = useRef();
if (!computedValueRef.current) {
// 只计算一次
computedValueRef.current = heavyComputation(value);
}
return <div>{computedValueRef.current}</div>;
}
与 useState
的关键区别
特性 | useRef |
useState |
---|---|---|
触发重渲染 | ❌ | ✅ |
存储类型 | 可变值 (current 属性) |
不可变状态 |
访问方式 | 直接访问 .current |
通过状态变量 |
更新机制 | 同步更新 | 异步批量更新 |
适合场景 | DOM引用/缓存值/避免重渲染计算 | UI渲染相关状态 |
高级用法
1. 自定义 Hook 封装
javascript
// 获取上一次值的Hook
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
2. 结合 forwardRef
暴露组件内部 DOM
jsx
const CustomInput = forwardRef((props, ref) => {
return <input ref={ref} {...props} />;
});
function Parent() {
const inputRef = useRef();
// 可直接操作 CustomInput 内部的 input
return <CustomInput ref={inputRef} />;
}
3. 测量 DOM 元素
jsx
function MeasureExample() {
const divRef = useRef(null);
const [size, setSize] = useState({});
useLayoutEffect(() => {
if (divRef.current) {
setSize({
width: divRef.current.offsetWidth,
height: divRef.current.offsetHeight
});
}
}, []);
return (
<div ref={divRef}>
元素尺寸: {size.width} x {size.height}
</div>
);
}
注意事项
-
不要在渲染期间修改 refs
修改
ref.current
应放在事件处理、useEffect 或生命周期方法中 -
避免过度使用
优先考虑 React 数据流(props/state),仅当确实需要时才使用 refs
-
初始渲染时 current 为 null
访问 DOM 元素时需做空值检查:
jsuseEffect(() => { if (inputRef.current) { inputRef.current.focus(); } }, []);
-
服务端渲染(SSR)问题
SSR 期间 refs 不会附加到 DOM,应在 useEffect 中使用
性能优化
jsx
function HeavyComponent() {
const elementRef = useRef();
// 避免在渲染期间进行昂贵操作
useEffect(() => {
if (elementRef.current) {
performExpensiveDOMOperation(elementRef.current);
}
}, []);
return <div ref={elementRef}>...</div>;
}
总结
useRef
是 React 函数组件的"逃生舱",主要解决两类问题:
- 访问 DOM 元素(表单聚焦、媒体控制、动画等)
- 存储可变值(计时器ID、缓存数据、前值记录等)
正确使用原则:
- 需要直接操作 DOM 时使用
- 需要存储与渲染无关的值时使用
- 避免在渲染期间修改
.current
- 结合
forwardRef
暴露组件内部 DOM - 优先使用 React 数据流,ref 作为最后手段