iT邦幫忙

2022 iThome 鐵人賽

DAY 4
1

https://ithelp.ithome.com.tw/upload/images/20220917/20151334OxAML3qPPE.png

前一篇 useEffect 大概了解之後,我們這篇來簡單了解一下 dependecies (就是辣個 []) 以及 useCallback 吧!

前一篇:https://codesandbox.io/s/03-02-useeffect-jkvtb9
本篇:https://codesandbox.io/s/04-usecallback-5hjnnp?file=/src/Score.js

(突然想到多拉A夢也是藍色的)

Dependecies

前一篇我們知道,useEffect 中提供的 dependecies(依賴) 來判斷需不需要執行。當我們不論是更改 minScore 或是執行 fetchData(),這個 component 都會重新執行,我們可以放一組 Math.random() 來看看輸出,不論更改分數或重新抓取資料,輸出的 random 每次都不一樣,除此之外,我們的在裡面所建立的 function 也都不一樣!

const random = Math.random()
console.log(random) //每次都不一樣

//我也不會都是同一個 function
const fetchData = async () => {
  setIsPending(true);
  setList([]);
  const data = await getPlayerList({ minScore });
  setList(data);
  setIsPending(false);
};

上一次,我們透過更改 minScore 來使 useEffect 重新取得資料,而 React 是使用 Object.is 來比對[]裡面的值有沒有更新。

Object.is 可以想像成進階版的 ===


還記得前一篇的 DEMO 中出現的黃色毛毛蟲嗎? (還是綠色?)

這時候我們可以先嘗試依照 eslint 的提示把 fetchData 也加入 []

React.useEffect(() => {
    //略
}, [minScore, fetchData]);

很好,現在開始進行無限的 loop 了 (╬゚д゚)

回到先前講述到 Object.is ,fetchData 裡面包含了 state(ex: list, isPending) 的控制,而 fetchData 每次render 都會是不同的 instance,放入到 [] 中自然比對上就會是不一樣的了,也同時形成了 loop,這時候有兩種做法:

  • useCallback
  • fetchData() 放到 useEffect 裡面

useCallback

Usage

const memorizedFunction = useCallback(() => {
  //...
}, [])

or

const func = () => {...}
const memorizedFunction = useCallback(func, [])

這邊的 []useEffect[] 是一樣的功能

Example

我們就照著這樣的語法給他~套起乃!

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

這樣一來就不會 loop 了!

不過這樣也換這個 function 出現毛毛蟲啦! (怒)

useCallback 把這個 fucntion 記起來,但現在的 fetchData() 不論怎麼取資料,由於已經被記憶住了,因此 minScore 在這個 function 裡面的值會永遠是 0,其他的呢? setState 有 React 保障,不論如何都會是同一個 function ; 而我們的 getPlayerList() 也是在 component 外引進來; data 本身只存活在這個 scope,這樣一來……,就是 minScore 了!

比照辦理,我們也可以把 minScore 進一步加進[]

const fetchData = React.useCallback(async () => {
    //略
},[minScore]); //加入minScore

我們也可以把 minScore 改以參數的方式傳進來(如下),就不用擔心記住的問題, fetchData() 也可以一直保持著同一個 function:

const fetchData = React.useCallback(async (minScore) => {
  //前略
  const data = await getPlayerList({ minScore });
  //後略
},[]);

useEffect(()=> {
  //..前略
  fetchData(minScore)
  //..後略
},[minScore])

放到 useEffect 裡邊去

(請用台語念這個標題)

若不使用 useCallback,也可以把 fetchData() 移駕到 useEffect 裡面使用:

React.useEffect(() => {
  //移駕近來  
  const fetchData = async () => {
    //略
  };

  const timeout = setTimeout(() => {
    console.log("fetching...");
    fetchData();
  }, 1000);

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

運作下來也沒問題,太讚啦!

useCallback 必要嗎?

「看起來 useCallback 真的很方便,那就把所有 function 都套上去使用吧!

當然是不行,useCallback 雖然能提升效能,幫我們記憶,但這個動作並不是沒有代價的,最好的比喻就是:記憶吐司可以一直吃的嗎?

https://ithelp.ithome.com.tw/upload/images/20220917/20151334fXJBICDunk.png

(然後大雄就拉肚子了)

那什麼時候會用到呢?當把 function 透過 props 傳遞,需要 reference equality:

function Foo({bar}) {
  React.useEffect(() => {
    const options = { bar }
    buzz(options)
  }, [bar])
  return <div>foobar</div>
}

function Blub() {
  const bar = React.useCallback(() => {}, [])
  return <Foo bar={bar} />
}

引用自此篇文章

可以看到 buzz 接受了一個 object,其中的 bar 是一個 function,而 bar 透過 prop 被傳遞進來,當 useEffect 知道 bar 變更時,就會執行。

這樣的情境下就能確保 bar 的傳遞時仍是一樣的 function,而當變更時能就進一步有不同的處理。

結語

  • useCallback 並不一定需要使用,依據情境的不同再適當的加入。
  • useCallback 並不會提升很多效能,若真的感受到卡卡,先從原本的function的邏輯下手檢查看看。
  • 後續實作 custom hook 時,由於沒辦法確定使用者會如何使用 function,因此仍會使用到許多 useCallback 。

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


上一篇
[DAY 03] useEffect 與無止盡 loop 的距離
下一篇
[DAY 05] useMemo 讓你網頁不卡卡
系列文
React Hook 不求人,建立自己的 Hook Libary30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言