1. 为什么我们需要理解React的Ref机制?
在React开发中,我们经常听到"避免直接操作DOM"的建议,但有时我们又不得不直接访问DOM元素或组件实例。这就是Ref机制存在的意义。想象一下,你正在开发一个视频播放器组件,需要精确控制播放、暂停等操作;或者你需要测量某个DOM元素的实际尺寸;又或者你需要集成第三方库(如D3.js)直接操作DOM。这些场景下,Ref就是你的最佳选择。
Ref(引用)是React提供的一种"逃生舱"机制,它允许我们直接访问DOM节点或在组件挂载后保持可变值。与state不同,修改Ref不会触发组件重新渲染,这使得它非常适合存储那些不需要触发UI更新的数据。
在React 16.8引入Hooks之前,我们主要使用createRef和回调Ref。而现在,useRef成为了函数组件中的首选方式。但Ref的使用远不止于此,当我们需要在组件树中传递Ref时,forwardRef就派上了用场。理解这些API如何协同工作,是掌握React高级用法的关键一步。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. useRef深度解析:不只是DOM引用
2.1 useRef的基本用法
useRef是一个返回可变ref对象的Hook,这个对象在组件的整个生命周期内保持不变。最基本的用法是获取DOM引用:
javascript复制function TextInput() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return (
<>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus Input</button>
</>
);
}
这里有几个关键点需要注意:
- useRef初始化时传入null是常见做法,因为ref.current在组件挂载前确实为null
- 我们通过ref属性将ref对象附加到DOM元素上
- 访问DOM节点需要通过ref.current属性
2.2 useRef作为实例变量的高级用法
useRef的强大之处在于,它不仅可以引用DOM,还可以存储任何可变值:
javascript复制function Timer() {
const intervalRef = useRef();
const [count, setCount] = useState(0);
useEffect(() => {
intervalRef.current = setInterval(() => {
setCount(c => c + 1);
}, 1000);
return () => clearInterval(intervalRef.current);
}, []);
const stopTimer = () => {
clearInterval(intervalRef.current);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={stopTimer}>Stop</button>
</div>
);
}
在这个例子中,我们使用useRef来存储interval ID。为什么不用state?因为interval ID不需要触发重新渲染,使用state反而会导致不必要的更新。
重要提示:修改ref.current不会触发组件重新渲染。如果你需要某个值变化时触发渲染,应该使用state而不是ref。
2.3 useRef与闭包陷阱
理解useRef如何避免闭包陷阱是掌握React高级用法的关键。考虑以下计数器组件:
javascript复制function Counter() {
const [count, setCount] = useState(0);
const countRef = useRef(count);
u
