iT邦幫忙

2023 iThome 鐵人賽

DAY 16
0
Modern Web

react 學習記錄系列 第 16

[Day16]我的 react 學習記錄 - useLayoutEffect

  • 分享至 

  • xImage
  •  

這篇文章的主要內容

簡單介紹 useLayoutEffect。


useLayoutEffect

useLayoutEffect 是另一種版本的 useEffect,不同的是他的執行時間是在瀏覽器 repaints 之前。

Syntax

useLayoutEffect(setup, dependencies?)

setup: 一個 function 放著 effect 的邏輯,跟 useEffect 一樣可以 return 一個 cleanup function。
dependencies?: 一個 array,放著 setup function 裡面使用到的外部變數,當元件 re-render 時會使用 Object.is() 一個一個做比較,如果其中一個是 false 的時候會執行 clean up function 然後再執行 setup function。

注意事項

  • 執行 useLayoutEffect 的時候會 block 瀏覽器繪製畫面,如果過度使用會影響網頁的效能,盡量減少使用。
  • useLayoutEffect 只能在元件裡使用,且只能在元件的最外層使用,不能放在迴圈或是判斷式裡,如果有需要可以建立一個新的子元件,放在子元件裡。
  • strict mode 的情況下,在元件初始化的時候 react 會快速的 mount -> unmount -> mount 你的元件,如果出現了預期外的錯誤有可能你會需要一個 clean up function 來處理。

useLayout Effect 可以想成是 useEffect 的一種變體,只是他執行的時間跟 useEffect 不同,所以就直接來看 code 吧,一樣從執行的時間開始看起。


執行時間

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

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

  useLayoutEffect(() => {
    console.log("useLayoutEffect");
    return () => {
      console.log("useLayoutEffect-return");
    };
  });

  useEffect(() => {
    console.log("useEffect");
    return () => {
      console.log("useEffect-return");
    };
  });

  const onClick = () => setCount(count + 1);

  return (
    <div>
      <h1>useLayoutEffect</h1>
      <div>Count: {count}</div>
      <button onClick={onClick}>add count</button>
    </div>
  );
}

結果。

useLayoutEffect-1

初次 render: useLayoutEffect -> useEffect

re-render: useLayoutEffect-cleanup -> useLayoutEffect -> useEffect-cleanup -> useEffect

上面有說到盡量減少不要使用到 useLayoutEffect 會影響效能,那接著就來看看什麼情況下會需要用到 useLayoutEffect 吧。

假設有一個 content 的 div,元素的高度是未定的,可能會因為用戶的裝置導致元素高度不定。

但是我希望我的 popup 的 div 可以永遠都出現在 content 的正下方,所以在 content 出現之前我的 popup 的位置是沒有辦法確定的,必須要透過 useEffect 來另外設定 popup 的位置。

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

function App() {
  const [isShow, setIsShow] = useState(false);
  const [top, setTop] = useState(0);
  const [height, setHeight] = useState(0);
  const divRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    // 使用 useEffect
    if (isShow && divRef.current) {
      // 取得元素位置跟高度,確認 popup 的位置
      setTop(divRef.current.offsetHeight + divRef.current.offsetTop);
    }
  }, [isShow]);

  function handleClick() {
    setIsShow(!isShow);
    if (!isShow) {
      // content 元素出現的時候高度未知
      setHeight(Math.random() * 50 + 20);
    }
    if (isShow) {
      // content 元素隱藏的時候把重置位置
      setTop(0);
    }
  }
  return (
    <div>
      <button onClick={handleClick}>Show Popup</button>
      {isShow && (
        <>
          <div
            ref={divRef}
            style={{
              height: `${height}px`,
              border: "solid 1px black",
            }}
          >
            content
          </div>
          <div
            style={{
              position: "absolute",
              width: "100%",
              backgroundColor: "#f00",
              top: `${top}px`,
            }}
          >
            Popup
          </div>
        </>
      )}
    </div>
  );
}

useLayoutEffect-2

會發現 pop 的位置會出現不正常的跳動,因為 useEffect 在執行的時候瀏覽器已經執行了 repaint 的動作,所以元素已經出現在畫面上了,才又更新元素的位置,使用 useLayoutEffect 就可以避免這個元素跳動的問題。

useLayoutEffect(() => {
  // 改用 useLayoutEffect
  if (isShow && divRef.current) {
    setTop(divRef.current.offsetHeight + divRef.current.offsetTop);
  }
}, [isShow]);

useLayoutEffect

因為 useLayoutEffect 執行的時間是在瀏覽器完成 reflow 之後 repaint 之前,這個時候已經知道元素位置跟大小,所以就可以在這個時候設定 popup 的位置,然後才執行 repaint,所以就可以避免元素的不自然跳動。

下一篇簡單介紹 custom hook。
如果內容有誤再麻煩大家指教,我會盡快修改。

這個系列的文章會同步更新在我個人的 Medium,歡迎大家來看看 👋👋👋
Medium


上一篇
[Day15]我的 react 學習記錄 - useReducer
下一篇
[Day17]我的 react 學習記錄 - custom hook
系列文
react 學習記錄30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言