文章中英模式
布鲁斯前端React面试题目 - useRef用途与实现原理
深入了解React useRef的多种用途、内部实现原理与使用技巧。掌握如何使用useRef存取DOM元素、保存变量而不触发重新渲染,理解其与useState的区别,以及在面试中常见的useRef相关问题与解答。
文章中英模式

懒得看文章?那就来看视频吧
useRef的基本介绍与实用性
useRef 是 React 提供的一個 Hook,用於創建一個可以在組件生命週期內持續存在的可變引用。它最大的特點是:當 ref 的內容改變時,不會觸發組件重新渲染。
渲染周期中的useRef vs useState
状态变化与渲染关系:
useRef
渲染1: ref.current = 0
修改值: ref.current = 1
→ 不触发重新渲染
渲染仍为1: ref.current = 1
useState
渲染1: [value=0, setValue]
修改值: setValue(1)
→ 触发重新渲染
渲染2: [value=1, setValue]
引用一致性比较:
useRef 保持同一引用
useRef 在組件的整個生命週期中返回同一個引用對象。即使組件重新渲染,ref 對象的身份也保持不變,只有 current 屬性的值可能改變。
// 首次渲染
const ref = useRef(0); // ref = { current: 0 }
// 多次渲染後,ref仍是同一個對象
console.log(ref === 上次渲染的ref); // trueuseState 每次渲染产生新值
useState 在每次渲染時會產生當前狀態的新快照。雖然狀態值可能相同,但每次渲染中的狀態變量都是獨立的常量。
// 渲染1
const [count, setCount] = useState(0);
// 這個渲染中的count是0的快照
// 渲染2 (after setCount(1))
const [count, setCount] = useState(0);
// 這個渲染中的count是1的新快照实际案例:计数器但不重新渲染
function SilentCounter() {
const [, forceUpdate] = useState({});
const countRef = useRef(0);
return (
<>
<p>計數: {countRef.current}</p>
<button onClick={() => {
// 增加計數但不重新渲染
countRef.current += 1;
console.log("計數已增加到:", countRef.current);
}}>
增加計數 (不更新UI)
</button>
<button onClick={() => forceUpdate({})}>
手動更新UI
</button>
</>
);
}useRef的三大主要用途
1. 存取DOM元素
这是useRef最常见的用途,直接访问DOM节点来执行命令式操作(如:聚焦、播放视频、计算尺寸)。
- 1. 需要操作DOM元素
- 2. 存储不影响UI的数据(计时器ID等)
- 3. 需要记住值但不想触发重新渲染
- 1. 建立一个具有current属性的普通JavaScript对象
- 2. 确保这个对象在不同渲染之间保持稳定的引用
- 3. 不提供任何主动通知机制当ref内容变更时
function AutoPlayVideo() {
const videoRef = useRef(null);
useEffect(() => {
// 元件掛載後自動播放
videoRef.current.play();
return () => {
// 元件卸載前暫停
videoRef.current.pause();
};
}, []);
return <video ref={videoRef} src="https://example.com/video.mp4" />;
}2. 保存可变的值(不触发重新渲染)
useRef可以存储任何类型的值,且在更新时不会触发重新渲染,适合保存那些不需要参与渲染计算的数据。
function IntervalCounter() {
const [count, setCount] = useState(0);
// 保存interval ID,便於清理
const intervalRef = useRef(null);
// 記錄最後一次渲染時的count值
const previousCountRef = useRef(0);
useEffect(() => {
// 保存前一次的count(會在每次渲染後執行)
previousCountRef.current = count;
// 設置interval
intervalRef.current = setInterval(() => {
setCount(c => c + 1);
}, 1000);
return () => {
// 清理interval
clearInterval(intervalRef.current);
};
}, []);
return (
<div>
現在: {count}, 之前: {previousCountRef.current}
</div>
);
}3. 缓存计算结果(类似memoization)
当需要存储昂贵计算的结果,但又不希望每次重新渲染都重新计算时,useRef可作为轻量级的替代方案。
function SearchResults({ query }) {
const [results, setResults] = useState([]);
// 用ref快取前一次搜尋結果
const prevQueryRef = useRef('');
const cachedResultsRef = useRef({});
useEffect(() => {
// 如果查詢條件沒變,或者我們已經有快取結果,直接使用
if (query === prevQueryRef.current && cachedResultsRef.current[query]) {
setResults(cachedResultsRef.current[query]);
return;
}
// 否則執行搜尋,並儲存結果
fetchSearchResults(query).then(data => {
setResults(data);
prevQueryRef.current = query;
cachedResultsRef.current[query] = data;
});
}, [query]);
return (
<ul>
{results.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}useRef vs useState:何时使用哪一个?
主要区别
| 特性 | useState | useRef |
|---|---|---|
| 更新时触发重新渲染 | 是 ✅ | 否 ❌ |
| 值的存取方式 | 直接使用变量 | 通过.current属性 |
| 更新方式 | 使用setter函数 | 直接修改.current |
| 重新渲染时保持值 | 是 ✅ | 是 ✅ |
| 适用场景 | 需要显示在UI上的数据 | 不需参与渲染的值,或DOM引用 |
选择指南
什么时候使用 useState:
- 1. 当数据变化需要立即反映在UI上
- 2. 当需要引起组件重新渲染以更新视图
- 3. 当该值会影响渲染结果或其他state/effect依赖
什么时候使用 useRef:
- 1. 存储不影响渲染输出的值(计时器ID、前一次的props等)
- 2. 需要直接访问DOM元素
- 3. 需要存储在组件生命周期内持续存在但不触发更新的值
- 4. 优化性能,避免不必要的重新渲染
// 考慮這個計數器例子,兩種實現方式的區別
function CounterExample() {
// 更新時會重新渲染
const [stateCount, setStateCount] = useState(0);
// 更新時不會重新渲染
const refCount = useRef(0);
return (
<>
<div>
<h3>useState計數器: {stateCount}</h3>
<button onClick={() => setStateCount(c => c + 1)}>
增加state計數
</button>
</div>
<div>
<h3>useRef計數器: {refCount.current}</h3>
<button
onClick={() => {
refCount.current += 1;
// 注意: 這裡需要手動強制重新渲染才能看到更新
console.log('useRef值已更新,但UI不會自動更新');
}}
>
增加ref計數 (不會顯示變化)
</button>
</div>
</>
);
}useRef 的内部实现原理
理解useRef的实现可以让我们更好地把握其行为特性。在React内部,useRef的实现非常简单:
// React內部useRef的簡化實現
function useRef(initialValue) {
const hookIndex = currentComponent.currentHookIndex++;
if (!currentComponent.hooks[hookIndex]) {
currentComponent.hooks[hookIndex] = { current: initialValue };
}
return currentComponent.hooks[hookIndex];
}实际上,useRef实现的核心在于:
- 1. 建立一个具有
current属性的普通 JavaScript 对象 - 2. 确保这个对象在不同渲染之间保持稳定的引用
- 3. 不提供任何主动通知机制当ref内容变更时
在React的Fiber架构中,useRef的状态也保存在Fiber节点的memoizedState中,作为Hook链表的一部分:
// 在React Fiber中的Hook結構
{
memoizedState: {
/* useState的Hook節點 */
// ...
next: {
// useRef的Hook節點
memoizedState: { current: initialValue },
next: {
/* 下一個Hook */
// ...
}
}
}
}进阶技巧与模式
使用useRef保存上一次的props或state
在某些情景下,需要比较当前值与前一次的值,useRef是理想的工具:
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
// 每次渲染後更新ref的值
ref.current = value;
}, [value]);
// 返回前一次的值
return ref.current;
}
function ProfileUpdater({ userId }) {
const prevUserId = usePrevious(userId);
useEffect(() => {
// 只有當用戶切換時才執行
if (prevUserId !== userId) {
console.log(`用戶從 ${prevUserId} 切換到 ${userId}`);
fetchUserData(userId);
}
}, [userId, prevUserId]);
// 組件其餘部分
}条件性DOM引用
当元素可能不存在或条件渲染时,需要小心处理refs:
function ConditionalRefExample({ showInput }) {
const inputRef = useRef(null);
// 與useLayoutEffect結合使用,確保DOM操作安全
useLayoutEffect(() => {
// 安全地檢查元素是否存在
if (showInput && inputRef.current) {
inputRef.current.focus();
}
}, [showInput]);
return (
<div>
{showInput && (
<input ref={inputRef} type="text" placeholder="我會自動聚焦" />
)}
{/* 沒有元素時ref.current為null */}
</div>
);
}🔥 常见面试题目
(一) useRef是什么?它有哪些主要用途?
解答: useRef是React的一个Hook,创建一个「记忆盒子」,即使组件重新渲染,里面的东西也不会丢失。
useRef盒子
.current
修改不会触发重新渲染
主要用途:
- 1. 抓取DOM元素(如:让输入框自动聚焦)
- 2. 存储不需触发重新渲染的数据
- 3. 记住前一个状态值
- 4. 保存计时器ID等需跨渲染周期的值
(二) useRef与useState有什么区别?
useState
修改值 → 触发重新渲染
使用setter函数更新
直接访问: value
useRef
修改值 → 不触发重新渲染
直接修改.current
访问: ref.current
(三) 为什么修改useRef不会触发重新渲染?
解答: 这与React的设计有关:
React渲染机制简图
useState
↓
通知React更新
↓
触发重新渲染
useRef
↓
静默更新.current
↓
React不知道变化
简单来说:
- 1. useState有「通知机制」,告诉React需要更新UI
- 2. useRef只是个容器,修改时没有通知React
- 3. React只关注通过正规渠道(如setState)的更新请求