Lala Code

Lala 的前端大補帖,歡迎一起鑽研前端技術😊

0%

【DAY 17】useEffect 處理副作用

cover

[情境劇場]

解師傅:餐廳請了歌手來駐唱,讓生意越來越好了,卻引來了隔壁小吃店的不滿,在我們版上留了負評

小當家:人紅是非多…這也是沒辦法的事,金錢才是王道啊!!


Side Effect 副作用

在介紹 useEffect 先來認識什麼是 Effect,Effect 指的是「Side Effect」,簡稱 Effect ,中文是副作用的意思。

我們最常聽到的副作用,也就是醫生開藥的藥單上面都會寫哪些藥,上面寫著作用跟副作用,作用是緩解鼻塞、流鼻水,可能會伴隨的副作用是會想睡覺

在 JavaScript 的也是這樣的存在,「在執行函式或行為時,會導致原有的狀態被改變或有附加功能」,就稱之為 Side Effect,例如:改變全域變數狀態、發送 HTTP Request、手動操作 DOM 元素 、改變系統狀態等等





認識 useEffect

useEffect 就是來處理 Side Effect 的 Hook,早期的 Class Component 是使用生命週期 (lifecycle)來管理組件函式,但這讓相同邏輯的函式,被迫拆開在不同的生命週期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class FriendStatus extends React.Component {
constructor(props) {
super(props);
this.state = { isOnline: null };
this.handleStatusChange = this.handleStatusChange.bind(this);
}

componentDidMount() {
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
handleStatusChange(status) {
this.setState({
isOnline: status.isOnline
});
}

render() {
if (this.state.isOnline === null) {
return 'Loading...';
}
return this.state.isOnline ? 'Online' : 'Offline';
}
}

因此 useEffect 的設計在將其保持在一起,易於管理,在程式碼上也較簡潔許多
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);

useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);

return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});

if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}



useEffect 使用方法

  • 從 react 中載入 useEffect
  • useEffect 帶入一個函式,函式裡帶入要執行的 effect
  • 如有 dependencies,將 dependencies 帶入第二個參數陣列


1. 從 react 中載入 useEffect

1
import { useEffect } from 'react';



2. useEffect 帶入一個函式,函式裡帶入要執行的 effect

1
2
3
useEffect(() => {
//...要執行的 effect
})

useEffect 會在參數中帶入一個函式,而這個函式會在「畫面渲染完成」後被呼叫,函式裡面放入畫面完成後需要執行的 effect





3. 如有 dependencies,將 dependencies 帶入第二個參數陣列

1
2
3
useEffect(() => {
//...要執行的 effect
}, [dependencies])

useEffect 第二個參數為一個陣列,陣列裡會放入需要重新渲染 effect 的依賴,我們稱之為 dependencies,如 dependencies 有變動,才會重新執行 effect





看個範例



這是一個單純的計數器功能,一開始畫面渲染完畢後,會接著執行 useEffect,執行結束後,一直到變更了 count 的值,才會再執行一次 useEffect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { useState, useEffect } from "react";

export default function App() {
const [count, setCount] = useState(0);

useEffect(() => {
console.log("useEffect");
}, [count]);

return (
<div>
<h1>Count: {count}</h1>
<button
onClick={() => {
setCount((state) => state + 1);
}}
>
Add Count
</button>
</div>
);
}

嘿!為什麼畫面一開始就渲染兩次 ?


為什麼畫面渲染完畢後,會跑了兩次 console.log?

你可以點擊 src/index.js 檔案,會看到裡面有 Strict Mode 嚴格模式

1
2
3
4
5
root.render(
<StrictMode>
<App />
</StrictMode>
);

Strict Mode 在開發模式下,為了檢測到渲染期生命週期的預期之外的 Side Effect,故意調用函式兩次,來幫助我們發現 Side Effect

這些函式有

  • Class component constructorrender 和 shouldComponentUpdate 方法
  • Class component 的靜態 getDerivedStateFromProps 方法
  • Function component 的內容
  • 狀態更新函式(setState 的第一個參數)
  • 函數傳遞至 useStateuseMemo 或 useReducer


如果我們在 index.js 下了 console.log(”index.js”),會發現 index.js 只渲染了一次

useEffect

問題就很清楚了,Strict Mode 的開發模式下確實會渲染兩次

Strict Mode 只會在開發模式中執行,故不會調用在正式環境




useEffect 使用的四種方式

1. 只執行在畫面渲染後

1
2
3
useEffect(() => {
console.log("mounted");
}, [])

不依賴於任何 props 或 state 的值,不需要重新執行的函式或行為,可以直接傳遞一個空陣列
如果你有學過 Class Component,就會像是 lifecycle 中的 componentDidMount





2. 組件更新就執行

1
2
3
useEffect(() => {
console.log("updated");
})

只要任何組件發生變動就會執行,會像是 class lifecycle 中的 componentDidUpdate





3. 組件 dependencies 有變更才執行

1
2
3
useEffect(() => {
console.log("updated with dependencies");
}, [count])

dependencies 發生改變才執行,如範例中的 count 如有變更,才會觸發 console.log,避免在每次 render 都進行昂貴的計算





4. 清理需被銷毀的函式

清理函式會在組件每次重新渲染時執行,先清除上次留下來的 effect,useEffect 會做的 4 個步驟:

  1. 判斷第二個參數的陣列是否一樣,如果一樣才會繼續
  2. 執行上一次存下來的清理函式
  3. 執行useEffect的內容
  4. 把 清理函式 存下來,供下次使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
useEffect(() => {
const onMousedown = () => {
console.log("mousedown");
};
window.addEventListener("mousedown", onMousedown);

const timer = setInterval(() => {
console.log('Hello React');
}, 1000);

return () => {
console.log("cleanup");
window.removeEventListener("mousedown", onMousedown);
clearInterval(timer);
};
});

在 useEffect 函式裡 return 一個函式,這個函式就是清除 effect 的函式,就像是 class lifecycle 中的 componentWillUnmount ,如果沒有將 effect 清除,當組件重新渲染,都會執行一個新的事件監聽,這很可能會發生錯誤,也會讓效能下降,常見的需要清除的函式如:setInterval、setTimeout、addEventListener…等




結語

認識了 useEffect 跟使用方法,讓我們得以處理 side effect,useEffect 跟之前的 class lifecycle 相比好寫很多,把相同邏輯放在一起也舒服很多~一起練習看看吧!





Reference

官方 Effect Hook


本文為 IT 鐵人賽系列文 你 React 了嗎? 30 天解鎖 React 技能




Hey!想學習更多前端知識嗎?

最近 Lala 開了前端課程 👉【實地掌握RWD - 12小時新手實戰班】👈
無論您是 0 基礎新手,又或是想學 RWD 的初學者,
我們將帶你從零開始,深入了解並掌握 RWD 響應式網頁設計的核心技術,快來一起看看吧 😊



🚀線上課程分享

線上課程可以加速學習的時間,省去了不少看文件的時間XD,以下是我推薦的一些課程
想學習更多關於前後端的線上課程,可以參考看看。

Hahow

Hahow 有各式各樣類型的課程,而且是無限次數觀看,對學生或上班族而言,不用擔心被時間綁住



六角學院

如果你是初學者,非常推薦六角學院哦!
剛開始轉職也是上了六角的課,非常的淺顯易懂,最重要的是,隨時還有線上的助教幫你解決問題!


Udemy

Udemy 裡的課程非常的多,品質普遍不錯,且價格都滿實惠的,CP值很高!
也是很多工程師推薦的線上課程網站。
❤️