useEffect hook 最主要的作用在於處理與畫面無關的 side effect,並非是 functional component 的生命週期API,因為這個處理副作用的 hook 不論呼叫幾次都不應該影響資料流或是程式邏輯。
useEffect 的 dependencies 機制設計的目的是為了讓 React 能夠正確地同步資料,並且優化效能。
在 dependencies 中傳入 side effect 函式所需要依賴的資料,react 就會在每一個渲染時使用object.is()
來比較前後兩次的 dependencies 依賴的原始資料否有變化,若有變化就會執行 side effect 函式,沒有就可以跳過這次 side effect 的執行。
dependencies 並非是為了控制 side effect 執行的時機或商業邏輯,而是為了讓 react 可以正確地同步化資料,所以應誠實地填寫有依賴到的原始資料到 dependencies 中。
import React, { useEffect, useState } from "react";
function App() {
useEffect(() => {
console.log("Component rendered");
}, []);
return <div>My Component</div>;
}
運作流程:
render:
appendChild()
將生成的 DOM element 掛載到實際的瀏覽器 DOM 上。re-render: 假設之後因畫面更新觸發 re-render,由於 dependencies 為空陣列,react 會比較前後兩次的 dependencies 依賴的原始資料,發現沒有變化,因此不會執行 side effect。
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>
);
}
運作流程:
appendChild()
將生成的 DOM element 掛載到實際的瀏覽器 DOM 上。setCount((prevCount)=>prevCount+1)
,count 的值更新到 1。object.is()
檢查前後兩次的 count 值,發現有變化,因此進入 reconciliation 階段,重新 render。object.is()
檢查 useEffect
dependencies 中所有的項目,發現所依賴的資料 count 值有變化,因此執行 side effect,印出 "Component rendered"。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:
appendChild()
將生成的 DOM element 掛載到實際的瀏覽器 DOM 上。re-render: 假設之後因畫面更新觸發 re-render,沒有傳入 dependencies,因此沒有參考的依賴資料,只要重新渲染就會執行一次 side effect,印出 "Component rendered"。