React hook之useRef
React useRef 详解
useRef
是 React 提供的一个 Hook,用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途,下面我将全面详细地介绍它的特性和用法。
基本概念
1. 创建 ref
const refContainer = useRef(initialValue);
initialValue
: ref 对象的初始值(.current
属性的初始值)- 返回一个可变的 ref 对象,其
.current
属性被初始化为传入的参数
2. 核心特性
- 跨渲染周期保存值:ref 对象在组件的整个生命周期内保持不变
- 修改不会触发重新渲染:改变
.current
属性不会导致组件重新渲染 - 直接访问 DOM 元素:最常见的用途之一
主要用途
1. 访问 DOM 元素
最常见的用法是访问 JSX 渲染的 DOM 元素:
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. 存储可变值
可以存储任何可变值,类似于类组件中的实例属性:
function Timer() {const intervalRef = useRef();useEffect(() => {intervalRef.current = setInterval(() => {console.log('Timer tick');}, 1000);return () => clearInterval(intervalRef.current);}, []);// ...
}
3. 保存上一次的值
实现获取上一次 props 或 state 的功能:
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 执行两次的问题:
useEffect(() => {const executedRef = useRef(false);if (!executedRef.current) {executedRef.current = true;// 你的初始化代码}
}, []);
高级用法
1. 转发 refs (forwardRef)
将 ref 传递给子组件:
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:
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 库集成
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
:// 错误示例 function MyComponent() {const myRef = useRef();myRef.current = 123; // 不应该在渲染期间修改return <div>{myRef.current}</div>; // 也不应该依赖渲染期间的值 }
-
ref 不会自动通知内容变化:
- 如果需要在 ref 变化时执行代码,使用回调 ref 或手动监听
-
多个 refs 合并:
function 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:
// 不好 - 每次渲染都创建新 ref function Component() {return <Child ref={useRef()} />; }// 好 - ref 只创建一次 function Component() {const ref = useRef();return <Child ref={ref} />; }
-
大量 ref 的内存问题:
- 当需要为列表中的每个元素创建 ref 时,考虑使用 ref 回调函数 或 第三方库
总结
useRef
是 React Hooks 中一个非常实用的工具,它:
- 提供了一种访问 DOM 节点的方式
- 可以存储不会触发重新渲染的可变值
- 在组件的整个生命周期内保持引用不变
- 是集成第三方库和实现高级模式的利器