@React / 2023-10-27

useLayoutEffect

useLayoutEffect 공식 문서 캡쳐 화면

useLayoutEffect 공부해보기 (스터디 발표 자료)

💡 useLayoutEffect는 performance에 영향을 줄 수 있다. 가능하면 useEffect를 사용하라.

useLayoutEffect란

화면을 렌더링 하기 전에 작업을 수행하는 useEffect.

사용법

생김새

useLayoutEffect(setup, dependencies?)

useEffect와 동일하다.

주의사항

useLayoutEffect 내의 코드 및 이로 인해 예약된 모든 상태 업데이트는 브라우저가 화면을 다시 그리는 것을 차단한다. 이를 지나치게 사용하면 앱이 느려질 수 있다. 따라서 가능하면 useEffect를 사용해야 한다.

예시

버튼에 hover시 tooltip이 뜨도록 구현된 코드.

import { useRef, useLayoutEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import TooltipContainer from './TooltipContainer.js';

export default function Tooltip({ children, targetRect }) {
  const ref = useRef(null);
  const [tooltipHeight, setTooltipHeight] = useState(0);

  useLayoutEffect(() => {
    const { height } = ref.current.getBoundingClientRect();
    setTooltipHeight(height);
  }, []);

  let tooltipX = 0;
  let tooltipY = 0;
  if (targetRect !== null) {
    tooltipX = targetRect.left;
    tooltipY = targetRect.top - tooltipHeight;
    if (tooltipY < 0) {
      // It doesn't fit above, so place below.
      tooltipY = targetRect.bottom;
    }
  }

  return createPortal(
    <TooltipContainer x={tooltipX} y={tooltipY} contentRef={ref}>
      {children}
    </TooltipContainer>,
    document.body,
  );
}

React는 useLayoutEffect 내부의 코드 및 그 안에서 예약된 모든 상태 업데이트가 브라우저가 화면을 다시 repaint 하기 전에 처리될 것을 보장한다. 이는 곧 tooltip을 처음 렌더링하고 재평가하고, 다시 재렌더링하게 되는 과정에서 사용자는 이를 인지하지 못하게 된다. 다시 말해, useLayoutEffect은 browser가 painting하는 것을 막는다.

아래는 같은 코드를 useEffect를 이용하여 구현한 것이다. 렌더링 속도를 늦추기 위해 약간의 코드가 추가되어 있다.

import { useRef, useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import TooltipContainer from './TooltipContainer.js';

export default function Tooltip({ children, targetRect }) {
  const ref = useRef(null);
  const [tooltipHeight, setTooltipHeight] = useState(0);

  // 렌더링 속도 일부로 낮추기
  let now = performance.now();
  while (performance.now() - now < 100) {
    // 아무것도 하지 않는다,,,
  }

  useEffect(() => {
    const { height } = ref.current.getBoundingClientRect();
    setTooltipHeight(height);
  }, []);

  let tooltipX = 0;
  let tooltipY = 0;
  if (targetRect !== null) {
    tooltipX = targetRect.left;
    tooltipY = targetRect.top - tooltipHeight;
    if (tooltipY < 0) {
      tooltipY = targetRect.bottom;
    }
  }

  return createPortal(
    <TooltipContainer x={tooltipX} y={tooltipY} contentRef={ref}>
      {children}
    </TooltipContainer>,
    document.body,
  );
}

useEffect는 비동기로 처리가 되며, Effect가 paint 전에 실행된다고 보장하지 않는다. 따라서 tooltip의 초기 위치(0)가 잠시 표시될 수 있고 이로 인한 깜빡거림이 발생할 수 있다.

Copyright ©2024, Designed By Eonseok Jeon