請解釋 useEffect?與 useLayoutEffect 的區別?

2023年2月2日

💎 加入 E+ 成長計畫 與超過 250+ 位軟體工程師一起在社群中成長,並且獲得更深入、系統性軟體工程內容

useEffect 是 React 中常用的一個 Hook,也是前端面試中經常被問到的 React 面試題,包括如何使用 useEffect、useEffect 的執行時機等。另一個與 useEffect 相似的 Hook 是 useLayoutEffect,這兩者的比較也是 React 面試的高頻題。

useEffect 是什麼?

useEffect 是一個用於連接外部系統的 React Hook。React 的函式元件需要是純函式,但如果我們需要執行具有副作用 (side effect) 的操作,例如:請求 API、使用第三方函式庫,我們就需要將這些程式碼放在 useEffect 中執行。外部系統例如是:伺服器端、瀏覽器提供的 API 或是第三方函式庫。因為這部分不是由 React 本身處理的,所以稱為外部系統。

useEffect 使用方法規則

只能在頂部呼叫

useEffect 也是一種 hook,因此只能在頂層呼叫,不能在迴圈、條件式或者巢狀的 function 中使用,想了解細節的朋友,可以前往這篇文章《為什麼只能在最頂端層呼叫 Hook?從 useState 實作原理來回答》

useEffect 接受兩個參數:setup function 和 dependencies(可選)

  1. setup function

    setup function:setup function 包含如何連結外部系統的程式碼,如果需要清除邏輯,可以在 setup function 中回傳一個清除 function。

  2. dependencies dependencies 參數是可選的陣列,可以傳入 props、state 或元件中任何使用的變數。React 會使用 Object.is 算法來進行比較, (想了解 Object.is 的細節可以閱讀此篇文章《在 JavaScript 當中,==、=== 與 Object.is ()的區別》)。如果 dependencies 中任意一個值與前一次不同,則此 useEffect 會重新執行。。

import { useEffect } from "react";
import { createConnection } from "./blog.js";

function Article({ articleId }) {
  const [serverUrl, setServerUrl] = useState("https://blog.com/0");

  useEffect(() => {
    const connection = createConnection(serverUrl, articleId);
    connection.connect();

    // 回傳 cleanip function
    return () => {
      connection.disconnect();
    };
  }, [serverUrl, articleId]);
  // ...
}

useEffect 執行時機

  1. 當元件被加入時 (mount),useEffect 會被第一次執行。
  2. 當每次元件重新渲染時,如果 dependencies 的值有改變,先將舊的 props 和 state 執行 cleanup function,再帶著新的 props 和 state 執行 setup function。
  3. cleanup function 的程式碼,會在元件生命週期結束 (unmount) 時,執行最後一次。

使用 useEffect,畫面重新渲染時都會閃爍要怎麼解?

解法:嘗試將 useEffect 換成使用 useLayoutEffect

以下這一段我們會討論跟 useEffect 很相近的 Hook - useLayoutEffect

而標題已經點出來,useLayoutEffect 通常會拿來處理當使用 useEffect 但畫面會出現閃爍的情境,詳細原因下面會提到。

useLayoutEffect 是什麼?

useLayoutEffect 其實是 useEffect 的一種版本,傳入的參數也一樣,只是執行時機不一樣,它會在瀏覽器重繪 (repaints) 前執行。

useLayoutEffect 可能會造成性能的問題,因為在 useLayoutEffect 裡的程式碼會阻礙瀏覽器重繪 (repaints) ,太頻繁使用可能會造成整個應用程式緩慢。因此通常會建議先使用 useEffect,如果不能解決問題,才會選擇使用 useLayoutEffect

useEffectuseLayoutEffect 比較

以下提供一個程式碼範例,可以明顯感到這兩者差別。 (備註:以下程式碼範例是為了凸顯這兩者差別,實際上開發並不會這樣寫)

程式碼

import { useEffect, useLayoutEffect, useState } from "react";

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

  useEffect(() => {
    if (count === 0) {
      const randomNum = 1 + Math.random() * 1000;
      setCount(randomNum);
    }
  }, [count]);

  return <div onClick={() => setCount(0)}>{count}</div>;
}

當我們執行上方程式碼時,連續點擊 div 區塊,會看到畫面產生閃爍。

原因是,當你每次點擊 div,此時 count 會被更新為 0,畫面會重新渲染變為 0,同時,因為 count 被更新,也會觸發 useEffect 執行。所以在重繪完成之後, useEffect 執行並把 count 更新為另一串隨機數字,畫面也會再渲染一次,因為兩次渲染時間很快,所以造成閃爍。

useLayoutEffect 的差異是什麼 ?

假設我們把上方程式碼的 useEffect 換成 useLayoutEffect,當你每次點擊 div,此時 count 會被更新為 0,但這時,畫面不會被重新渲染變為 0,而是先等待 useLayoutEffect 內的程式碼執行完畢之後,state 已經更新為新的隨機數字,這時畫面才進行重繪。

延伸問題

如果不指定 dependencies ,useEffect 什麼時候會執行?

此 effect 會是在每次重新渲染元件後重新執行

如果 dependencies 中有 object 會怎麼樣?

下方程式碼的 options 物件會在元件每次重新渲染時,都是不同的物件,所以這個 useEffect 可能會在每次渲染時都重新執行,因為在 dependencies 中的 options 有改變。

function ChatRoom({ articleId }) {
  const [article, setArticle] = useState(null);

	// options 會在每次渲染時重新創建
  const options = {
    serverUrl: 'https://localhost:1234',
    articleId: articleId
  };

  useEffect(() => {
    const data = getArticle(options);
    setArticle(data)

	// options 每次渲染時值都不同,因此觸發 useEffect 執行
  }, [options]);

如果要避免上方程式碼造成不必要觸發 useEffect,其實可以把動態的物件放在 Effect 中,並把 dependencies 中的物件改為 string 或 number,如下。

function ChatRoom({ articleId }) {
  const [article, setArticle] = useState(null);

  useEffect(() => {
	  const options = {
	    serverUrl: 'https://localhost:1234',
	    articleId: articleId
	  };

    const data = getArticle(options);
    setArticle(data)

  }, [articleId]);

什麼時候使用 useLayoutEffect

如上方說到,useLayoutEffect 頻繁使用會損害應用程式的效能,只有在一些特別情況才建議使用。以下提供一個使用情境 (此例子來自於 React Docs Beta)。

假設今天我們要設計一個工具提示元件 (tooltip),它會依照不同條件出現在某元素的上方、下方或旁邊,這種設計條件代表說,我們會需要知道此元素準確的高度位置,才能判斷要將 tooltip 顯示在哪裡。

React 的渲染步驟可以拆分為下

  • 在任何地方渲染 Tooltip (即使位置不正確)
  • 測量元素的高度,並決定放置 Tooltip 的位置
  • 重新渲染畫面,這時 Tooltip 的位置才會是正確的

如果是使用 useEffect 的話,Tooltip 的位置,可能是從 0 變到 10 的位置,這會造成畫面閃爍、使用者體驗不佳,如果是使用 useLayoutEffect,React 則會在重繪前,就重新計算正確的位置,才渲染畫面。

程式碼可參考此連結 React Docs Beta

🧵 如果你想收到最即時的內容更新,可以在 FacebookInstagram 上追蹤我們