簡單介紹 useLayoutEffect。
useLayoutEffect
是另一種版本的 useEffect,不同的是他的執行時間是在瀏覽器 repaints 之前。
useLayoutEffect(setup, dependencies?)
setup
: 一個 function 放著 effect 的邏輯,跟 useEffect 一樣可以 return 一個 cleanup function。dependencies?
: 一個 array,放著 setup function 裡面使用到的外部變數,當元件 re-render 時會使用 Object.is()
一個一個做比較,如果其中一個是 false 的時候會執行 clean up function 然後再執行 setup
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>
);
}
結果。
初次 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>
);
}
會發現 pop 的位置會出現不正常的跳動,因為 useEffect 在執行的時候瀏覽器已經執行了 repaint 的動作,所以元素已經出現在畫面上了,才又更新元素的位置,使用 useLayoutEffect 就可以避免這個元素跳動的問題。
useLayoutEffect(() => {
// 改用 useLayoutEffect
if (isShow && divRef.current) {
setTop(divRef.current.offsetHeight + divRef.current.offsetTop);
}
}, [isShow]);
因為 useLayoutEffect 執行的時間是在瀏覽器完成 reflow 之後 repaint 之前,這個時候已經知道元素位置跟大小,所以就可以在這個時候設定 popup 的位置,然後才執行 repaint,所以就可以避免元素的不自然跳動。
下一篇簡單介紹 custom hook。
如果內容有誤再麻煩大家指教,我會盡快修改。
這個系列的文章會同步更新在我個人的 Medium,歡迎大家來看看 👋👋👋
Medium