Jeff的隨手筆記

學習當一個前端工程師

0%

『Day 28』Effect 的生命週期

前言

在專案開發的過程中,處理副作用一直是個重要的課題。記得剛接觸 React 時,總是把 useEffect 跟傳統的生命週期掛鉤,導致寫出來的程式碼既複雜又難以維護。直到後來深入了解了 Effect 的本質 - 同步機制,才發現原來我們一直用錯了方向。今天就讓我分享這個重要的觀念轉變,以及在實務上如何正確運用 Effect。

Effect 與元件生命週期的差異

傳統上,React 元件的生命週期分為:

  1. Mount(掛載):元件首次被加入畫面
  2. Update(更新):因 props 或 state 變更而重新渲染
  3. Unmount(卸載):元件從畫面中移除

但 Effect 的運作方式其實完全不同:

1
2
3
4
5
6
7
8
9
10
11
12
function ChatRoom({ roomId }) {
useEffect(() => {
// 這不是 "Mount",而是「開始同步」
const connection = createConnection(roomId);
connection.connect();

// 這不是 "Unmount",而是「結束同步」
return () => {
connection.disconnect();
};
}, [roomId]);
}

Effect 的同步機制

重新同步的時機

Effect 會在以下情況重新執行:

  1. 依賴項改變
  2. 元件重新渲染(只在依賴項發生變化時)

同步的過程是這樣的:

  1. 先執行清理函式,停止舊的同步
  2. 然後執行 Effect 主體,開始新的同步
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function VideoPlayer({ src, isPlaying }) {
useEffect(() => {
const video = videoRef.current;

if (isPlaying) {
video.play(); // 開始播放
} else {
video.pause(); // 暫停播放
}

return () => {
video.pause(); // 清理:確保影片暫停
};
}, [isPlaying]);
}

響應式值與依賴關係

什麼是響應式值?

在 React 中,響應式值包括:

  1. Props:從父元件傳入的值
  2. State:使用 useState 建立的狀態
  3. 計算值:由 props 或 state 衍生的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function DataFetcher({ query, pageSize }) {
const [pageIndex, setPageIndex] = useState(0);

// 這是一個響應式值
const queryString = `?query=${query}&page=${pageIndex}&size=${pageSize}`;

useEffect(() => {
async function fetchData() {
const response = await fetch('/api/data' + queryString);
// ...處理回應
}
fetchData();
}, [queryString]); // queryString 必須加入依賴陣列
}

依賴項的重要性

遺漏依賴項可能導致:

  1. 使用到過期的值
  2. 無法及時反應資料變化
  3. 產生不預期的行為

進階概念:全域和可變值

特殊情況處理

有些值不適合作為依賴項:

  1. 全域變數(如 window.location)
  2. Ref 的 current 屬性
  3. 可變物件

處理這類值的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function WindowTracker() {
const [path, setPath] = useState(window.location.pathname);

useEffect(() => {
// 正確處理全域變數變化
function handlePathChange() {
setPath(window.location.pathname);
}

window.addEventListener('popstate', handlePathChange);
return () => {
window.removeEventListener('popstate', handlePathChange);
};
}, []);
}

最佳實踐

  1. Effect 設計原則

    • 每個 Effect 只負責一個獨立的同步邏輯
    • 提供完整的清理機制
    • 確保能處理多次執行的情況
  2. 常見陷阱與解決方案

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // ❌ 避免這樣做
    useEffect(() => {
    setCount(count + 1);
    }, [count]);

    // ✅ 改用 state updater
    useEffect(() => {
    setCount(c => c + 1);
    }, []);

  3. 效能考量

    • 避免不必要的依賴
    • 適當拆分 Effect
    • 使用記憶化(useMemo)減少重複計算

總結

理解 Effect 的同步本質,能幫助我們:

  1. 寫出更清晰的副作用處理邏輯
  2. 避免生命週期的思維陷阱
  3. 正確處理資源的訂閱與清理
  4. 建立更容易維護的程式碼

最重要的是要記住:Effect 不是生命週期事件,而是一個同步的過程。當我們轉換這個思維模式,很多原本覺得複雜的問題都會變得清晰許多。