iT邦幫忙

2022 iThome 鐵人賽

DAY 3
2

30 天的旅途前段會來回頭認識一些基本的 React Hook,繼 useState 再來就是 useEffect。

useEffect 如其名,專門拿來處理 Side Effect,不論你想要串接API,操作DOM,或是想執行非同步function(像是setTimeout) 通通都在這邊運作,那就來看看怎麼使用吧!

Usage

import {useEffect} from 'react'

useEffect(() => {
    //執行的動作
},[])

useEffect 會在畫面 render 完成之後執行一些動作,根據使用方式執行方法也略有不同:

  • 當畫面完成 render 時,會執行 useEffect:
useEffect(() => {
  console.log("每次都有我")
})

//mount
console.log("每次都有我")
//update
console.log("每次都有我")
console.log("每次都有我")
console.log("每次都有我")
...
  • useEffect 中若回傳一個 callback 為 clean-up fucntion:
useEffect(() => {
  console.log("啊!水灑了...")
  
  return () => console.log("我來清理!")
})

//第一次render後
console.log("啊!水灑了...")

//後續render
console.log("我來清理!")
console.log("啊!水灑了...")

//後續render
console.log("我來清理!")
console.log("啊!水灑了...")

//後續render
console.log("我來清理!")
console.log("啊!水灑了...")
...
  • 加上 [] dependecies 時:
    條件式的 useEffect,當提供的值有所改變時,就會執行,反之若甚麼都不提供(如下),只會執行最初的一次
useEffect(() => {
  console.log("你只會看見我一次")
}, [])

//第一次render後
console.log("你只會看見我一次")
  • 加上 clean-up[] 時:
useEffect(() => {
  console.log("你只會看見我一次")
  
  return () => console.log("再見!")
},[])

//第一次render後
console.log("你只會看見我一次")

//後續render
//nothing happened

//unmount
console.log("再見!")

Example 01

與 Demo 一起伴讀吧

前一篇中我們使用了 useState 來記錄 player 的分數,現在我們加入 useEffect 來看看當分數改變時,render 之間 useEffect 是如何輸出的!

如果覺得眼花,可以先把 <StrictMode/> 以及局部的 useEffect 給註解掉

React.useEffect(() => {
  console.log("[Panel]01 useEffect: Mounted!");

  return () => console.log("[Panel]01 useEffect: Cleaning up!");
});

React.useEffect(() => {
  console.log("[Panel]02 useEffect: Mounted!");

  return () => console.log("[Panel]02 useEffect: Cleaning up!");
}, []);

React.useEffect(() => {
  console.log("[Panel]03 useEffect: Mounted!");

  return () => console.log("[Panel]03 useEffect: Cleaning up!");
}, [score]);

當進入畫面之後,預期的輸出會是:

console.log("[Panel]01 useEffect: Mounted!");
console.log("[Panel]02 useEffect: Mounted!");
console.log("[Panel]03 useEffect: Mounted!");

當 score 變更時:

console.log("[Panel]01 useEffect: Cleaning up!");
console.log("[Panel]03 useEffect: Cleaning up!");
console.log("[Panel]01 useEffect: Mounted!");
console.log("[Panel]03 useEffect: Mounted!");

Example 02

一定要配溫 Demo

常見的情況就是用來與API互動。

可以在 Demo 的 api/player 找到模仿的 api 以及假資料。

假設今天活動結束後,我們要瀏覽所有 Player 的分數:

  • list 是用來儲存抓取回來的資料
  • isPending 則是用來判斷資料是否抓取完成,可進一步告知使用者正在讀取中
  • fetchData 則是用來打假API的function
const [list, setList] = useState([]);
const [isPending, setIsPending] = useState(true);

useEffect(() => {
  fetchData();
}, []);

const fetchData = async () => {
  setIsPending(true);
  setList([]);
  const data = await getPlayerList();
  setList(data);
  setIsPending(false);
};

然後教練說:「讓我們來看看有誰分數高於10吧!」(哪來的教練?

於是我們建立了 minScore 當作假API的 query:

const [list, setList] = useState([]);
const [isPending, setIsPending] = useState(true);
const [minScore, setMinScore] = useState(0);

useEffect(() => {
  fetchData();
}, [minScore]); //把 minScore 當作 re-fetch 的 trigger

const fetchData = async () => {
  setIsPending(true);
  setList([]);
  const data = await getPlayerList({ minScore }); //如同api query
  setList(data);
  setIsPending(false);
};

但這樣延伸出了一個問題:「只要使用者不停地輸入,fetchData() 就會一直執行」

教練:「不能給我隨便按ㄛ?」

我們可以加入 debounce / input disabled 的方式來避免這種可怕的情況

const [list, setList] = useState([]);
const [isPending, setIsPending] = useState(true);
const [minScore, setMinScore] = useState(0);

useEffect(() => {
  const timeout = setTimeout(() => {
    fetchData();
  }, 1000);

   return () => clearTimeout(timeout);
}, [minScore]);

const fetchData = async () => {
  setIsPending(true);
  setList([]);
  const data = await getPlayerList({ minScore });
  setList(data);
  setIsPending(false);
};

...

<input disabled={isPending} />

useEffect 加入 debounce 的方法,當使用者輸入內容時,會觸發 setTimeout 並在一秒後執行 fetchData(),若使用者輸入很快時,useEffect 的 clean-up function 也會先行執行,以避免太多不必要的 fetching;也使用了 isPending 來提供畫面的 loading 呈現,來告知使用者資料正在抓取中。

Loop 在哪裡?

單純就是開發過程中如果忘記加上 [] 就會...

useEffect(() => {
  fetchData();
});

//OR

useEffect(() => {
  setState(state + 1)
}, [state]); //如果加上了但放錯內容就會...(情境提供參考^_^)

後端:「難道要被攻擊了嗎OAO」

結語

  • 可以感受到 useState 越來越多,之後會再來看看怎麼變成 custom hook。
  • Demo 中可以看到 useEffect 的 dependecies 出現捲捲底線毛毛蟲,我們保留到下一篇 useCallback 再來說說。
  • 原本想比對生命週期(life cycle),但筆者沒有寫過Class component,就沒有這個篇幅;根據官方文件「表示」可以視為一樣,但實際上 useEffect 是等到畫面渲染完成之後才會執行,執行順序與觸發點仍不太一樣,供參考。不過既然都進入 Hook 的世界,就以 useEffect 的方式來思考吧!

若本系列有錯誤的內容,歡迎隨時跟我告知。


上一篇
[DAY 02] Hook I am? I am useState!
下一篇
[DAY 04] 記憶吐司的 useCallback
系列文
React Hook 不求人,建立自己的 Hook Libary30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言