iT邦幫忙

2023 iThome 鐵人賽

DAY 15
0
Modern Web

從Vue學React!不只要會用,還要真的懂~系列 第 15

【Day 15】究竟是watch?還是生命週期API?處理副作用的useEffect

  • 分享至 

  • xImage
  •  

寫Vue的時候,偶爾會需要使用watch去監聽某個state的變動,來去進行一些邏輯操作。在接觸React之後,我發現React也有一個類似用途的hook,這個hook就是useEffect。不過在最近好好認識這個hook後,才發現這個hook說是跟watch很類似,卻又有一點不太ㄧ樣。今天一樣從Vue的watch開始認識React的useEffect吧!

監聽值的變動來進行邏輯操作 - watch

首先,來快速看一下Vue的watch。就如同它的名字一樣,watch通常使用於監聽某些值的變動來做進一步的邏輯操作,例如我希望實作的效果是「當我點擊按鈕到數字變5時,印出console.log。」時,就可以透過watch監聽數字的改動。

<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

<script>
import { ref, watch } from 'vue';

export default {
  name: 'WatchExample',
  setup() {
    const count = ref(0);

    // 監聽 count 的變化
    watch(count, (newValue, oldValue) => {
      if (newValue === 5) {
        console.log('count 達到 5 了!');
      }
    });

    const increment = () => {
      count.value++;
    };

    return {
      count,
      increment,
    };
  },
};
</script>

以上這個情境就是透過監聽count的變動,來決定何時要顯示提視窗。
另外,watch除了監聽state的變動外,還可以取得變動前及變動後的state,來進行相對應的操作。

和watch有點像的React hook - useEffect

前面快速看完watch的用法和使用情境後,再來直接看一下前面提到的情境,用useEffect的話,可以怎麼做。
useEffect在使用上會傳入兩個參數,一個是 副作用的函式,在這個函式內還可以return一個執行清除副作用的函式,也被成為cleanup,而另一個參數則是與這個副作用有相依的 相依值陣列

useEffect(() => {
  // 要進行的操作
  console.log('side effect');
  // 清理函式
  return () => {
    console.log('Cleanup is happening');
  };
}, [dependencies]);

所以如果要實作「當我點擊按鈕到數字變5時,印出console.log。」的情境,就會是以下這樣的寫法。

useEffect(() => {
  // 要進行的操作
  if (count === 5) {
    console.log('count 達到 5 了!');
  }

}, [count]);

會監聽state變動,但不只是監聽state變動的useEffect

單純從前面的例子來看的話,大多數的人可能都會覺得useEffect跟watch很像,甚至會覺得「useEffect根本是watch吧!」,因為useEffect和watch一樣可以會因為特定state的變動,來進行一些邏輯的處理。但useEffect實際上並不只是在監聽state的變動,真的要說的話,它反而比較像是生命週期的API,但它卻也不是所謂的生命週期API。接下來就來透過一些實際的例子,了解useEffect的特性和使用概念吧!

useEffect固定會在第一次render的時候執行,也有可能會隨著render執行

useEffect除了固定會在第一次渲染的時候執行外,還有一個特性是當你沒有透過「第二個參數-相依值陣列」限制它要在因為哪些相依的state有變動而執行時,它其實會在每次渲染都被執行

例如這樣調整的話,其實是會在每次畫面重新渲染時,都執行useEffect。

 useEffect(() => {
   console.log("Effect is running");
   if (count === 5) {
     console.log("count 達到 5 了!");
   }
 // 不透過第二個參數傳入相依state的陣列
 }); 

https://i.imgur.com/MusuQck.gif

useEffect會隨著相依值的變動執行

當有傳入第二個參數,也就是dependicies陣列時,就只會當相依的值有變動時,才執行useEffect。

useEffect(() => {
  console.log("Effect is running");
  if (count === 5) {
    console.log("count 達到 5 了!");
  }
// 加上dependicies 陣列
}, [count]); 

https://i.imgur.com/wPnUO3O.gif

useEffect會在畫面重新渲染後,先執行上次的cleanup

為了避免前一次的副作用造成再次執行副作用時產生問題,不只在元件umount的時候會執行cleanup,在每次重新渲染後,也會先執行上次副作用的cleanup。

useEffect(() => {
  console.log('current: ', {count});
  return () => {
    console.log('clean up');
  };
}, [count]);

https://i.imgur.com/c9WQa0b.gif

useEffect的副作用函式是同步的

useEffect的副作用函式會在畫面渲染後執行,無法以非同步的方式執行,所當寫這樣的非同步函式當作副作用函式時,就會出現warning訊息。

useEffect(async () => {
  console.log('setup')
  return () => {
    console.log('clean up');
  };
}, []);

https://ithelp.ithome.com.tw/upload/images/20230919/20130914unp6nKwYnB.png

但是總有一些時機點,需要在處理副作用的時候,透過非同步的方式執行一些邏輯,這時可以改個做法,在副作用函式裡面宣告一個非同步的函式再使用。
例如:

useEffect(() => {
  const fetchData = async () => {
    const response = await getData();
  };
  
  fetchData()
}, []);

看了以上特性後,應該可以感覺得到其實useEffect真的不是單純的watch,也不算是生命週期API。

為什麼useEffect不算是watch,也不算是生命週期API?

現在可以再花一點時間回頭思考一下「為什麼useEffect實際上並不算監聽也不算是一個生命週期的hook」。useEffect在使用上,並不是單純地在監聽某個值,因為它在第一次渲染時,其實就會先執行一次useEffect的副作用函式,接下來再次渲染才會依照第二個參數有無帶入,或是帶入什麼相依值,而決定是否再次執行useEffect的副作用函式,並且會在umount的時候,或是下一個副作用函式執行前先進行清除前一次副作用的動作。useEffect雖然會隨著元件的生命週期執行,也會因為相依的值有變動而執行,但是它並不是用來監聽某個沒有關聯的值來處理邏輯,也不是只有在特定的生命週期才會執行,它更像是隨著整個functional component的資料變動,下去處理相關的副作用,所以它的用途應該定位在處理副作用,而不只是單純的監聽或生命週期API。

Strict Mode下的useEffect

當我們在未使用Strict Mode的時候,第一次render時,只會執行一次useEffect的副作用,第二次render才會先清掉上次的副作用,再進行一次副作用。但是當我們在開發模式中使用Strict Mode的時候,初次渲染就會先進行副作用,再進行cleanup,最後再進行一次副作用。所以當我們第一次進入畫面時,就可以看到console會呈現這個樣子。

setup -> cleanup -> setup
https://ithelp.ithome.com.tw/upload/images/20230919/20130914zkEBZMOL18.png

這個部分也跟元件在渲染時,都會經歷mount->unmount->mount一樣,都是在避免一些預期外的問題產生。

總結

今天了解了一個在React中,很重要的hook - useEffect,除了學習怎麼使用外,也了解了一些useEffec的特性。更重要的是知道它並不是單純的生命週期,也不是單純的watch,雖然用途或使用時機可能有點雷同,但因為它主要是在處理副作用,還是需要隨著相關state的變動下去思考它的使用時機點。雖然把它拿來監聽與副作用邏輯沒有直接關聯的state也會有作用,但是為了避免不預期的狀況出現,還是依照相依值陣列存在的用意,放上與副作用確實有相依的值會比較好。

參考資料

useEffect
useEffect 其實不是 function component 的生命週期 API


上一篇
【Day 14】設計樣式共用的元件!Vue有v-slot,那React可以怎麼做!?
下一篇
【Day 16】不要再重新計算啦!把計算複雜的值緩存起來 - computed & useMemo
系列文
從Vue學React!不只要會用,還要真的懂~30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言