uesEffect 其實不是 functional component 的 API
useEffect hook 最主要的作用在於處理與畫面無關的 side effect,並非是 functional component 的生命週期API,因為這個處理副作用的 hook 不論呼叫幾次都不應該影響的資料流或是程式邏輯。
useEffect 的 dependencies 機制設計的目的
useEffect 的 dependencies 機制設計的目的是為了讓 React 能夠正確地同步資料,並且優化效能。
在 dependencies 中傳入 side effect 函式所需要依賴的資料,react 就會在每一個渲染時使用
object.is
來比較前後兩次的 dependencies 依賴的原始資料否有變化,若有變化就會執行 side effect 函式,沒有就可以跳過這次 side effect 的執行。
dependencies 並非是為了控制 side effect 執行的時機或商業邏輯 ,而是為了讓 react 可以正確地同步化資料,所以應誠實地填寫有依賴到的原始資料到 dependencies 中。
在 useEffect 中,根據 dependencies 的不同情境運作的方式:
dependencies 為空陣列
import React, { useEffect, useState } from "react";
function App() {
useEffect(() => {
console.log("Component rendered");
}, []);
return <div>My Component</div>;
}
運作流程:
-
render:
- render 階段:react 執行 App component function 產出 react element。
- commit 階段:瀏覽器實際的 DOM 中並沒有相對應的 component 實例 DOM element,因此會將 react element 全部轉成相對應的實際 DOM element。然後使用瀏覽器的 DOM API
appendChild()
將生成的 DOM element 掛載到實際的瀏覽器 DOM 上。 - mount: 執行 side effect,印出 "Component rendered"。
-
re-render: 假設之後因畫面更新觸發 re-render,由於 dependencies 為空陣列,react 會比較前後兩次的 dependencies 依賴的原始資料,發現沒有變化,因此不會執行 side effect。
dependencies 中傳入 useState
import React, { useEffect, useState } from "react";
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("Component rendered");
}, [count]);
return (
<div>
<button onClick={() => setCount((prevCount) => prevCount + 1)}>
Click me
</button>
<div>{count}</div>
</div>
);
}
運作流程:
- render:
- render 階段:react 執行 App component function 產出 react element。
- commit 階 段:瀏覽器實際的 DOM 中並沒有相對應的 component 實例 DOM element,因此會將 react element 全部轉成相對應的實際 DOM element。然後使用瀏覽器的 DOM API
appendChild()
將生成的 DOM element 掛載到實際的瀏覽器 DOM 上。 - mount: 執行 side effect,印出 "Component rendered"。
- re-render:
- 當點擊 button 時觸發
setCount((prevCount)=>prevCount+1)
,count 的值更新到 1。 object.is()
檢查前後兩次的 count 值,發現有變化,因此進入 reconciliation 階段,重新 render。- render 階段:react 執行 App component function 產出 react element。
- commit 階段: 將前後兩次的 react element 進行樹狀結構的比較,找出差異,並且只會去操作新舊 react element 差異所對應的實際 DOM element。
- mount : 透過
object.is()
檢查useEffect
dependencies 中所有的項目,發現所依賴的資料 count 值有變化,因此執行 side effect,印出 "Component rendered"。
- 當點擊 button 時觸發
不傳入 dependencies
import React, { useEffect, useState } from "react";
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("Component rendered");
});
return (
<div>
<button onClick={() => setCount((prevCount) => prevCount + 1)}>
Click me
</button>
<div>{count}</div>
</div>
);
}
運作流程:
-
render:
- render 階段:react 執行 App component function 產出 react element。
- commit 階段:瀏覽器實際的 DOM 中並沒有相對應的 component 實例 DOM element,因此會將 react element 全部轉成相對應的實際 DOM element。然後 使用瀏覽器的 DOM API
appendChild()
將生成的 DOM element 掛載到實際的瀏覽器 DOM 上。 - mount: 執行 side effect,印出 "Component rendered"。
-
re-render: 假設之後因畫面更新觸發 re-render,沒有傳入 dependencies,因此沒有參考的依賴資料,只要重新渲染就會執行一次 side effect,印出 "Component rendered"。
總結:
- 空陣列:useEffect 只在初次 mount 時執行一次,不會再執行。
- 傳入依賴項:依賴項改變時執行副作用函式,狀態變化正確同步。
- 無依賴項:每次渲染都會執行副作用,這可能會導致不必要的效能問題。