React useRef 详解
useRef
是 React 提供的一个 Hook,用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途,下面我将全面详细地介绍它的特性和用法。
基本概念
1. 创建 ref
jsx
const refContainer = useRef(initialValue);
initialValue
: ref 对象的初始值(.current
属性的初始值)- 返回一个可变的 ref 对象,其
.current
属性被初始化为传入的参数
2. 核心特性
- 跨渲染周期保存值:ref 对象在组件的整个生命周期内保持不变
- 修改不会触发重新渲染 :改变
.current
属性不会导致组件重新渲染 - 直接访问 DOM 元素:最常见的用途之一
主要用途
1. 访问 DOM 元素
最常见的用法是访问 JSX 渲染的 DOM 元素:
jsx
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
2. 存储可变值
可以存储任何可变值,类似于类组件中的实例属性:
jsx
function Timer() {
const intervalRef = useRef();
useEffect(() => {
intervalRef.current = setInterval(() => {
console.log('Timer tick');
}, 1000);
return () => clearInterval(intervalRef.current);
}, []);
// ...
}
3. 保存上一次的值
实现获取上一次 props 或 state 的功能:
jsx
function Counter() {
const [count, setCount] = useState(0);
const prevCountRef = useRef();
useEffect(() => {
prevCountRef.current = count;
});
const prevCount = prevCountRef.current;
return (
<div>
<p>Current: {count}, Previous: {prevCount}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
4. 避免重复执行 useEffect
解决 React 18+ 严格模式下 useEffect 执行两次的问题:
jsx
useEffect(() => {
const executedRef = useRef(false);
if (!executedRef.current) {
executedRef.current = true;
// 你的初始化代码
}
}, []);
高级用法
1. 转发 refs (forwardRef)
将 ref 传递给子组件:
jsx
const FancyInput = React.forwardRef((props, ref) => {
return <input ref={ref} className="fancy-input" {...props} />;
});
function App() {
const inputRef = useRef();
useEffect(() => {
inputRef.current.focus();
}, []);
return <FancyInput ref={inputRef} />;
}
2. 回调 refs
动态设置多个 ref:
jsx
function MeasureExample() {
const [height, setHeight] = useState(0);
const measuredRef = useCallback(node => {
if (node !== null) {
setHeight(node.getBoundingClientRect().height);
}
}, []);
return (
<div ref={measuredRef}>
<h1>Hello, world</h1>
<p>The above header is {Math.round(height)}px tall</p>
</div>
);
}
3. 与第三方 DOM 库集成
jsx
function Canvas() {
const canvasRef = useRef(null);
useEffect(() => {
const ctx = canvasRef.current.getContext('2d');
// 使用第三方库绘制
new ThirdPartyLibrary(ctx);
}, []);
return <canvas ref={canvasRef} />;
}
useRef 与 useState 的区别
特性 | useRef | useState |
---|---|---|
触发重新渲染 | 否 | 是 |
值更新时机 | 同步 | 异步 |
适合存储 | DOM 引用、可变变量、计时器 | 需要触发 UI 更新的状态 |
访问方式 | .current 属性 |
直接访问状态变量 |
初始化 | 参数作为 .current 初始值 |
参数作为初始状态 |
注意事项
-
不要在渲染期间写入/读取
ref.current
:jsx// 错误示例 function MyComponent() { const myRef = useRef(); myRef.current = 123; // 不应该在渲染期间修改 return <div>{myRef.current}</div>; // 也不应该依赖渲染期间的值 }
-
ref 不会自动通知内容变化:
- 如果需要在 ref 变化时执行代码,使用回调 ref 或手动监听
-
多个 refs 合并:
jsxfunction useCombinedRefs(...refs) { return useCallback(el => { refs.forEach(ref => { if (!ref) return; if (typeof ref === 'function') ref(el); else ref.current = el; }); }, refs); }
-
服务端渲染(SSR)注意事项:
- ref 在服务端渲染时不会被序列化
- 在服务端和客户端渲染结果要保持一致
性能优化
useRef
本身是轻量级的,但以下情况需要注意:
-
避免在渲染函数中创建新 ref:
jsx// 不好 - 每次渲染都创建新 ref function Component() { return <Child ref={useRef()} />; } // 好 - ref 只创建一次 function Component() { const ref = useRef(); return <Child ref={ref} />; }
-
大量 ref 的内存问题:
总结
useRef
是 React Hooks 中一个非常实用的工具,它:
- 提供了一种访问 DOM 节点的方式
- 可以存储不会触发重新渲染的可变值
- 在组件的整个生命周期内保持引用不变
- 是集成第三方库和实现高级模式的利器