Jeff的隨手筆記

學習當一個前端工程師

0%

『Day 25』理解 React Refs 與 DOM 操作

前言

在前一篇討論完 useRef 的基本概念後,今天想要更深入地探討操作 DOM 時的細節。這些知識在處理第三方套件整合或是複雜動畫效果時特別重要。讓我分享一下實務上的一些經驗與觀察。

React 的 DOM 更新機制

React 的更新分為兩個階段:

  1. Render 階段:呼叫元件計算畫面內容
  2. Commit 階段:實際更新 DOM
    • 先將相關的 ref.current 設為 null
    • 更新 DOM 後立即設定對應的節點

這個機制讓我們必須注意存取 DOM 的時機:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function ScrollableList() {
const listRef = useRef(null);

// ❌ 渲染期間無法取得正確的 DOM
console.log(listRef.current.scrollHeight);

// ✅ 在 useEffect 中操作較安全
useEffect(() => {
console.log(listRef.current.scrollHeight);
}, []);

return <div ref={listRef}>{/* 內容 */}</div>;
}

使用 ref callback 處理動態元素

當需要處理動態列表時,ref callback 提供了更靈活的控制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Gallery() {
return images.map(img => (
<div ref={node => {
if (node) {
// 元素被加入 DOM
initializeImage(node);
} else {
// 元素被移除 DOM
cleanupImage(node);
}
}} />
));
}

同步更新與 flushSync

有時我們需要確保 DOM 已經更新完成:

1
2
3
4
5
6
7
8
9
10
import { flushSync } from 'react-dom';

function handleClick() {
flushSync(() => {
setCount(c => c + 1);
});
// DOM 已更新,可以安全取得最新狀態
const height = divRef.current.getBoundingClientRect().height;
}

DOM 操作的安全區域

雖然不建議直接修改 React 管理的 DOM,但以下情況是相對安全的:

  1. 非破壞性操作(focus、scroll 等)
  2. 操作 React 不會更新的區域(如永遠為空的容器)
1
2
3
4
5
6
7
8
9
10
11
function SafeContainer() {
const containerRef = useRef(null);

// ✅ 安全:這個 div 在 React 中永遠是空的
return (
<div ref={containerRef}>
{/* React 不會更新這裡的內容 */}
</div>
);
}

總結

深入了解 React 的 DOM 更新機制,讓我們能更好地掌握 Refs 的使用時機。記住以下幾點:

  1. 注意 DOM 操作的時機
  2. 善用 ref callback 處理動態元素
  3. 需要時使用 flushSync 確保同步更新
  4. 謹慎選擇 DOM 操作的範圍

這些進階知識能幫助我們更優雅地處理複雜的 DOM 操作需求。