์ฃผ์˜ํ•˜์„ธ์š”!

useLayoutEffect๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์„ฑ๋Šฅ์ด ์ €ํ•˜๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ€๋Šฅํ•˜๋‹ค๋ฉด useEffect๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.

useLayoutEffect๋Š” ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ํ™”๋ฉด์„ ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ ์ „์— ์‹คํ–‰๋˜๋Š” useEffect์ž…๋‹ˆ๋‹ค.

useLayoutEffect(setup, dependencies?)

๋ ˆํผ๋Ÿฐ์Šค

useLayoutEffect(setup, dependencies?)

useLayoutEffect๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ํ™”๋ฉด์„ ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ ์ „์— ๋ ˆ์ด์•„์›ƒ์„ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.

import { useState, useRef, useLayoutEffect } from 'react';

function Tooltip() {
const ref = useRef(null);
const [tooltipHeight, setTooltipHeight] = useState(0);

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

์•„๋ž˜์—์„œ ๋” ๋งŽ์€ ์˜ˆ์‹œ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.

๋งค๊ฐœ๋ณ€์ˆ˜

  • setup: The function with your Effectโ€™s logic. Your setup function may also optionally return a cleanup function. Before your component is added to the DOM, React will run your setup function. After every re-render with changed dependencies, React will first run the cleanup function (if you provided it) with the old values, and then run your setup function with the new values. Before your component is removed from the DOM, React will run your cleanup function.

  • optional dependencies: The list of all reactive values referenced inside of the setup code. Reactive values include props, state, and all the variables and functions declared directly inside your component body. If your linter is configured for React, it will verify that every reactive value is correctly specified as a dependency. The list of dependencies must have a constant number of items and be written inline like [dep1, dep2, dep3]. React will compare each dependency with its previous value using the Object.is comparison. If you omit this argument, your Effect will re-run after every re-render of the component.

  • ์„ ํƒ์‚ฌํ•ญ dependencies: setup์ฝ”๋“œ ๋‚ด์—์„œ ์ฐธ์กฐ๋œ ๋ชจ๋“  ๋ฐ˜์‘ํ˜• ๊ฐ’์˜ ๋ชฉ๋ก์ž…๋‹ˆ๋‹ค. ๋ฐ˜์‘ํ˜• ๊ฐ’์—๋Š” props, state, ๊ทธ๋ฆฌ๊ณ  ์ปดํฌ๋„ŒํŠธ ๋ณธ๋ฌธ์— ์ง์ ‘ ์„ ์–ธ๋œ ๋ชจ๋“  ๋ณ€์ˆ˜์™€ ํ•จ์ˆ˜๊ฐ€ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. linter๊ฐ€ React์šฉ์œผ๋กœ ์„ค์ •๋œ ๊ฒฝ์šฐ, ๋ชจ๋“  ๋ฐ˜์‘ํ˜• ๊ฐ’์ด ์˜์กด์„ฑ์œผ๋กœ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ง€์ •๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์˜์กด์„ฑ ๋ชฉ๋ก์—๋Š” ์ผ์ •ํ•œ ์ˆ˜์˜ ํ•ญ๋ชฉ์ด ์žˆ์–ด์•ผ ํ•˜๋ฉฐ [dep1, dep2, dep3]์™€ ๊ฐ™์ด ์ž‘์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. React๋Š” Object.is ๋น„๊ต ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ ์˜์กด์„ฑ์„ ์ด์ „ ๊ฐ’๊ณผ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค. ์˜์กด์„ฑ์„ ์ „ํ˜€ ์ง€์ •ํ•˜์ง€ ์•Š์œผ๋ฉด ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‹ค์‹œ ๋ Œ๋”๋งํ•  ๋•Œ๋งˆ๋‹ค Effect๊ฐ€ ๋‹ค์‹œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

๋ฐ˜ํ™˜๊ฐ’

useLayoutEffect๋Š” undefined๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

์ฃผ์˜ ์‚ฌํ•ญ

  • useLayoutEffect๋Š” Hook์ด๋ฏ€๋กœ, ์ปดํฌ๋„ŒํŠธ์˜ ์ตœ์ƒ์œ„ ๋ ˆ๋ฒจ ๋˜๋Š” ์ปค์Šคํ…€ Hook์—์„œ๋งŒ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ˜๋ณต๋ฌธ์ด๋‚˜ ์กฐ๊ฑด๋ฌธ ๋‚ด์—์„œ ํ˜ธ์ถœํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ด ์ž‘์—…์ด ํ•„์š”ํ•˜๋‹ค๋ฉด ์ƒˆ๋กœ์šด ์ปดํฌ๋„ŒํŠธ๋กœ ๋ถ„๋ฆฌํ•ด์„œ Effect๋ฅผ ์ƒˆ ์ปดํฌ๋„ŒํŠธ๋กœ ์˜ฎ๊ธฐ์„ธ์š”.

  • Strict Mode๊ฐ€ ์ผœ์ ธ ์žˆ์œผ๋ฉด, React๋Š” ์‹ค์ œ ์ฒซ ๋ฒˆ์งธ setup ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์ด์ „์— ๊ฐœ๋ฐœ ๋ชจ๋“œ์—๋งŒ ํ•œ์ •ํ•˜์—ฌ ํ•œ ๋ฒˆ์˜ ์ถ”๊ฐ€์ ์ธ setup + cleanup ์‚ฌ์ดํด์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” cleanup ๋กœ์ง์ด setup ๋กœ์ง์„ ์™„๋ฒฝํžˆ โ€œ๋ฐ˜์˜โ€ํ•˜๊ณ , setup ๋กœ์ง์ด ์ˆ˜ํ–‰ํ•˜๋Š” ์ž‘์—…์„ ์ค‘๋‹จํ•˜๊ฑฐ๋‚˜ ๋˜๋Œ๋ฆฌ๋Š” ์ง€๋ฅผ ํ™•์ธํ•˜๋Š” ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ์ž…๋‹ˆ๋‹ค. ์ด๋กœ ์ธํ•ด ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด cleanup ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ•˜์„ธ์š”.

  • ์˜์กด์„ฑ ์ค‘์— ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์—์„œ ์ •์˜๋œ ๊ฐ์ฒด๋‚˜ ํ•จ์ˆ˜๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ, Effect ๊ฐ€ ํ•„์š” ์ด์ƒ์œผ๋กœ ๋‹ค์‹œ ์‹คํ–‰๋  ์œ„ํ—˜์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๋ฉด ๋ถˆํ•„์š”ํ•œ ๊ฐ์ฒด ์˜์กด์„ฑ์ด๋‚˜ ํ•จ์ˆ˜ ์˜์กด์„ฑ์„ ์ œ๊ฑฐํ•˜์„ธ์š”. State ์—…๋ฐ์ดํŠธ๋‚˜ ๋น„ ๋ฐ˜์‘ํ˜• ๋กœ์ง์„ effect ๋ฐ–์œผ๋กœ ๋นผ๋‚ผ ์ˆ˜ ๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

  • Effect๋Š” ํด๋ผ์ด์–ธํŠธ ํ™˜๊ฒฝ์—์„œ๋งŒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. ์„œ๋ฒ„ ๋ Œ๋”๋ง ์ค‘์—๋Š” ์‹คํ–‰๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

  • useLayoutEffect ๋‚ด๋ถ€์˜ ์ฝ”๋“œ์™€ ์ด๋กœ ์ธํ•œ ๋ชจ๋“  state ์—…๋ฐ์ดํŠธ๋Š” ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ํ™”๋ฉด์„ ๋‹ค์‹œ ๊ทธ๋ฆฌ๋Š” ๊ฒƒ์„ ๋ง‰์Šต๋‹ˆ๋‹ค. ๊ณผ๋„ํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๋ฉด ์•ฑ์ด ๋А๋ ค์ง‘๋‹ˆ๋‹ค. ๊ฐ€๋Šฅํ•˜๋ฉด useEffect๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.

  • useLayoutEffect ๋‚ด๋ถ€์—์„œ state ์—…๋ฐ์ดํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด React๋Š” useEffect๋ฅผ ํฌํ•จํ•œ ๋‚˜๋จธ์ง€ ๋ชจ๋“  Effect๋ฅผ ์ฆ‰์‹œ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.


์‚ฌ์šฉ๋ฒ•

๋ธŒ๋ผ์šฐ์ €๊ฐ€ ํ™”๋ฉด์„ ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ ์ „์— ๋ ˆ์ด์•„์›ƒ ๊ณ„์‚ฐํ•˜๊ธฐ

๋Œ€๋ถ€๋ถ„์˜ ์ปดํฌ๋„ŒํŠธ๋Š” ๋ Œ๋”๋ง์„ ์œ„ํ•ด ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ์˜ ํ™”๋ฉด์ƒ ์œ„์น˜์™€ ํฌ๊ธฐ๋ฅผ ์•Œ ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์ปดํฌ๋„ŒํŠธ๊ฐ€ JSX๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ปดํฌ๋„ŒํŠธ์˜ ๋ ˆ์ด์•„์›ƒ(์œ„์น˜์™€ ํฌ๊ธฐ)๋ฅผ ๊ณ„์‚ฐํ•˜๊ณ  ํ™”๋ฉด์„ ๋‹ค์‹œ ๊ทธ๋ฆฝ๋‹ˆ๋‹ค.

๊ฐ€๋”์€ ์ด๊ฒƒ๋งŒ์œผ๋กœ๋Š” ๋ถ€์กฑํ•œ ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๋งˆ์šฐ์Šค ์ปค์„œ๋ฅผ ์˜ฌ๋ฆฌ๋ฉด ํˆดํŒ์ด ์š”์†Œ ์˜†์— ๋‚˜ํƒ€๋‚˜๋Š” ๊ฒฝ์šฐ๋ฅผ ์ƒ๊ฐํ•ด ๋ณด์„ธ์š”. ์ถฉ๋ถ„ํ•œ ๊ณต๊ฐ„์ด ์žˆ๋‹ค๋ฉด ํˆดํŒ์€ ์š”์†Œ ์œ„์— ๋‚˜ํƒ€๋‚˜๊ฒ ์ง€๋งŒ, ๊ณต๊ฐ„์ด ๋ถ€์กฑํ•˜๋‹ค๋ฉด ์•„๋ž˜์— ๋‚˜ํƒ€๋‚˜์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ฒฐ๊ตญ ํˆดํŒ์„ ์˜ฌ๋ฐ”๋ฅธ ์œ„์น˜์— ๋ Œ๋”๋งํ•˜๋ ค๋ฉด ํˆดํŒ์˜ ๋†’์ด๋ฅผ ์•Œ์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. (์œ„์ชฝ ๊ณต๊ฐ„์— ๋“ค์–ด๊ฐ€๋Š”์ง€ ํŒ๋‹จ ํ•ด์•ผ ํ•จ)

์ด๋ฅผ ์œ„ํ•ด ๋‘ ๋ฒˆ์˜ ๋ Œ๋”๋ง์„ ๊ฑฐ์ณ์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  1. ํˆดํŒ์„ (์ž˜๋ชป๋œ ์œ„์น˜๋ผ๋„) ์•„๋ฌด ์œ„์น˜์— ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค
  2. ํˆดํŒ์˜ ๋†’์ด๋ฅผ ๊ณ„์‚ฐํ•ด์„œ ํˆดํŒ์„ ๋ฐฐ์น˜ํ•  ์œ„์น˜๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.
  3. ์˜ฌ๋ฐ”๋ฅธ ์œ„์น˜์— ํˆดํŒ์„ ๋‹ค์‹œ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค.

์ด ์ž‘์—…์€ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ํ™”๋ฉด์„ ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ ์ „์— ๋ชจ๋‘ ์ด๋ฃจ์–ด์ ธ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํˆดํŒ์ด ์›€์ง์ด๋Š” ๊ฑธ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ณด์ด๊ณ  ์‹ถ์ง€ ์•Š์œผ๋‹ˆ๊นŒ์š”. useLayoutEffect๋ฅผ ํ˜ธ์ถœํ•ด์„œ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ํ™”๋ฉด์„ ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ ์ „์— ๋ ˆ์ด์•„์›ƒ์„ ๊ณ„์‚ฐํ•˜์„ธ์š”.

function Tooltip() {
const ref = useRef(null);
const [tooltipHeight, setTooltipHeight] = useState(0); // ์•„์ง ์‹ค์ œ ๋†’์ด๋ฅผ ๋ชจ๋ฆ…๋‹ˆ๋‹ค.

useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setTooltipHeight(height); // ์‹ค์ œ ๋†’์ด๋ฅผ ์•Œ์•˜์œผ๋‹ˆ ๋‹ค์‹œ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค.
}, []);

// ...์•„๋ž˜์— ์˜ฌ ๋ Œ๋”๋ง ๋กœ์ง์—์„œ tooltipHeight๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”...
}

์ž‘๋™ ๋ฐฉ์‹์„ ๋‹จ๊ณ„๋ณ„๋กœ ์•Œ์•„๋ด…์‹œ๋‹ค.

  1. Tooltip ์€ ์ดˆ๊ธฐํ™”๋œ ๊ฐ’์ธ tooltipHeight = 0์œผ๋กœ ๋ Œ๋”๋ง ๋ฉ๋‹ˆ๋‹ค (๋”ฐ๋ผ์„œ ํˆดํŒ์˜ ์œ„์น˜๋Š” ์ž˜๋ชป๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค).
  2. React๊ฐ€ ์ด ํˆดํŒ์„ DOM์— ๋ฐฐ์น˜ํ•˜๊ณ  useLayoutEffect ์•ˆ์˜ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  3. useLayoutEffect๊ฐ€ ํˆดํŒ์˜ ๋†’์ด๋ฅผ ๊ณ„์‚ฐํ•˜๊ณ  ๋ฐ”๋กœ ๋‹ค์‹œ ๋ Œ๋”๋ง์‹œํ‚ต๋‹ˆ๋‹ค.
  4. Tooltip ์ด ์‹ค์ œ tooltipHeight๋กœ ๋ Œ๋”๋ง ๋ฉ๋‹ˆ๋‹ค. (๋”ฐ๋ผ์„œ ํˆดํŒ์ด ์˜ฌ๋ฐ”๋ฅธ ์œ„์น˜์— ๋ฐฐ์น˜๋ฉ๋‹ˆ๋‹ค.)
  5. React๊ฐ€ DOM์—์„œ ์ด๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๊ณ  ๋งˆ์นจ๋‚ด ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ํˆดํŒ์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.

์•„๋ž˜์˜ ๋ฒ„ํŠผ๋“ค ์œ„๋กœ ๋งˆ์šฐ์Šค ์ปค์„œ๋ฅผ ์˜ฌ๋ ค์„œ ํˆดํŒ์ด ๊ณต๊ฐ„์— ๋“ค์–ด๊ฐ€๋Š”์ง€์— ๋”ฐ๋ผ ์œ„์น˜๋ฅผ ์กฐ์ •ํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•˜์„ธ์š”.

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);
    console.log('Measured tooltip height: ' + 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
  );
}

Tooltip ์ปดํฌ๋„ŒํŠธ๋Š” ๋‘ ๋ฒˆ์˜ ๋ Œ๋”๋ง์„ ๊ฑฐ์น˜์ง€๋งŒ (์ฒ˜์Œ์€ 0์œผ๋กœ ์ดˆ๊ธฐํ™”๋œ tooltipHeight๋กœ ๋ Œ๋”๋ง ๋˜๊ณ , ๊ทธ๋‹ค์Œ ์‹ค์ œ๋กœ ๊ณ„์‚ฐ๋œ ๋†’์ด๋กœ ๋ Œ๋”๋ง ๋จ), ์‹ค์ œ๋กœ ๋ณด์ด๋Š” ๊ฑด ์ตœ์ข… ๊ฒฐ๊ณผ๋ฟ์ž…๋‹ˆ๋‹ค. ์ด ์˜ˆ์‹œ์—์„œ useEffect ๋Œ€์‹  useLayoutEffect๊ฐ€ ํ•„์š”ํ•œ ์ด์œ ์ž…๋‹ˆ๋‹ค. ์•„๋ž˜์—์„œ ์ฐจ์ด์ ์„ ์ž์„ธํ•˜๊ฒŒ ์‚ดํŽด๋ด…์‹œ๋‹ค.

useLayoutEffect vs useEffect

์˜ˆ์‹œ 1 of 2:
useLayoutEffect ๋Š” ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ํ™”๋ฉด์„ ๋‹ค์‹œ ๊ทธ๋ฆฌ๋Š” ๊ฒƒ์„ ๋ง‰์Šต๋‹ˆ๋‹ค

React๋Š” useLayoutEffect ๋‚ด๋ถ€์˜ ์ฝ”๋“œ์™€ ์ด๋กœ ์ธํ•œ ๋ชจ๋“  state ์—…๋ฐ์ดํŠธ๊ฐ€ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ํ™”๋ฉด์„ ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ ์ „์— ์ฒ˜๋ฆฌ๋˜๋Š” ๊ฒƒ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. ๋•๋ถ„์— ํˆดํŒ์„ ๋ Œ๋”๋งํ•˜๊ณ , ์œ„์น˜์™€ ํฌ๊ธฐ๋ฅผ ๊ณ„์‚ฐํ•˜๊ณ  ๋‹ค์‹œ ๋ Œ๋”๋งํ•˜๋ฉด์„œ ์ฒซ ๋ฒˆ์งธ ๋ Œ๋”๋ง์€ ์‚ฌ์šฉ์ž๊ฐ€ ๋ชจ๋ฅด๊ฒŒ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰, useLayoutEffect๋Š” ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ํ™”๋ฉด์„ ๊ทธ๋ฆฌ๋Š” ๊ฒƒ์„ ๋ง‰์Šต๋‹ˆ๋‹ค.

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) {
      // ์œ„์ชฝ ๊ณต๊ฐ„์— ๋“ค์–ด๊ฐ€์ง€ ๋ชปํ•˜๋ฏ€๋กœ ์•„๋ž˜์— ๋ฐฐ์น˜ํ•ฉ๋‹ˆ๋‹ค.
      tooltipY = targetRect.bottom;
    }
  }

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

์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค!

๋‘ ๋ฒˆ์— ๊ฑธ์ณ์„œ ๋ Œ๋”๋งํ•˜๊ณ  ๋ธŒ๋ผ์šฐ์ €๋ฅผ ๋ง‰๋Š” ๊ฒƒ์€ ์„ฑ๋Šฅ์„ ์ €ํ•˜ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ€๋Šฅํ•˜๋ฉด ํ”ผํ•˜์„ธ์š”.


๋ฌธ์ œ ํ•ด๊ฒฐ

์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: โ€œuseLayoutEffect does nothing on the serverโ€

useLayoutEffect์˜ ๋ชฉ์ ์€ ๋ ˆ์ด์•„์›ƒ ์ •๋ณด๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

  1. ์ดˆ๊ธฐ ์ฝ˜ํ…์ธ ๋ฅผ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค.
  2. ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ํ™”๋ฉด์„ ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ ์ „์— ๋ ˆ์ด์•„์›ƒ์„ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
  3. ์ฝ์€ ๋ ˆ์ด์•„์›ƒ ์ •๋ณด๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ตœ์ข… ์ฝ˜ํ…์ธ ๋ฅผ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค.

์„œ๋ฒ„ ๋ Œ๋”๋ง์„ ์ง์ ‘ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ๋ผ๋ฉด, React ์•ฑ์€ ์„œ๋ฒ„์—์„œ ์ดˆ๊ธฐ ๋ Œ๋”๋ง์„ ํ•ด์„œ HTML์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด JavaScript ์ฝ”๋“œ๊ฐ€ ๋กœ๋“œ๋˜๊ธฐ ์ „์— ์ดˆ๊ธฐ HTML์„ ๋ณด์—ฌ์ฃผ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๋ฌธ์ œ๋Š” ์„œ๋ฒ„์—๋Š” ๋ ˆ์ด์•„์›ƒ ์ •๋ณด๊ฐ€ ์—†๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์•ž์„  ์˜ˆ์‹œ์—์„  Tooltip ์ปดํฌ๋„ŒํŠธ์—์„œ useLayoutEffect๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ํˆดํŒ์„ ์ฝ˜ํ…์ธ ์˜ ๋†’์ด์— ๋”ฐ๋ผ (์ฝ˜ํ…์ธ ์˜ ์œ„์ชฝ๊ณผ ์•„๋ž˜์ชฝ ์ค‘) ์˜ฌ๋ฐ”๋ฅธ ์œ„์น˜์— ๋ฐฐ์น˜ํ•ฉ๋‹ˆ๋‹ค. ์ดˆ๊ธฐ ์„œ๋ฒ„ HTML์˜ ์ผ๋ถ€๋กœ Tooltip์„ ๋ Œ๋”๋งํ•˜๋ ค ํ•˜๋ฉด, ์ด๋•Œ๋Š” ํˆดํŒ์˜ ์œ„์น˜๋ฅผ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ฒฐ์ •ํ•  ์ˆ˜ ์—†์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์„œ๋ฒ„์—์„œ๋Š” ์•„์ง ๋ ˆ์ด์•„์›ƒ ์ •๋ณด๊ฐ€ ์—†์œผ๋‹ˆ๊นŒ์š”! ๋”ฐ๋ผ์„œ ํˆดํŒ์„ ์„œ๋ฒ„์—์„œ ๋ Œ๋”๋งํ•˜๊ธฐ๋ฅผ ์›ํ•˜๋”๋ผ๋„, ํด๋ผ์ด์–ธํŠธ๋กœ ์˜ฎ๊ฒจ์„œ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๊ฐ€ ๋กœ๋“œ๋˜๊ณ  ์‹คํ–‰๋œ ํ›„์— ๋ Œ๋”๋งํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ผ๋ฐ˜์ ์œผ๋กœ ๋ ˆ์ด์•„์›ƒ ์ •๋ณด์— ์˜์กดํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๋Š” ์–ด์ฐจํ”ผ ์„œ๋ฒ„์—์„œ ๋ Œ๋”๋งํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค๋ฉด, ์ดˆ๊ธฐ ๋ Œ๋”๋ง ์ค‘์— Tooltip์ด ๋ณด์ด๋Š” ๊ฒƒ์€ ๋ง์ด ์•ˆ ๋ฉ๋‹ˆ๋‹ค. Tooltip์€ ํด๋ผ์ด์–ธํŠธ ์ƒํ˜ธ์ž‘์šฉ์— ์˜ํ•ด์„œ ๋ณด์ด๋Š” ๊ฒƒ์ด๋‹ˆ๊นŒ์š”.

๊ทธ๋Ÿผ์—๋„ ์ด๋Ÿฐ ๋ฌธ์ œ๋ฅผ ๋งˆ์ฃผ์นœ๋‹ค๋ฉด ๋ช‡ ๊ฐ€์ง€ ๋‹ค๋ฅธ ์˜ต์…˜์ด ์žˆ์Šต๋‹ˆ๋‹ค.

  • useLayoutEffect๋ฅผ useEffect๋กœ ๋Œ€์ฒด ํ•˜์„ธ์š”. ํ™”๋ฉด์„ ๊ทธ๋ฆฌ๋Š” ๊ฒƒ์„ ๋ง‰์ง€ ๋ง๊ณ  (์ดˆ๊ธฐ HTML์ด Effect ์‹คํ–‰ ์ „์— ๋ณด์ด๊ธฐ ๋•Œ๋ฌธ์—) ์ดˆ๊ธฐ ๋ Œ๋”๋ง์ด ๋ณด์ด๋”๋ผ๋„ ๊ดœ์ฐฎ๋‹ค๊ณ  React์—๊ฒŒ ๋งํ•ด์ฃผ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

  • ๋˜๋Š” ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ๋ฅผ ํด๋ผ์ด์–ธํŠธ ์ „์šฉ์œผ๋กœ ๋งŒ๋“œ์„ธ์š”. React๊ฐ€ ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด <Suspense> ๊ฒฝ๊ณ„ ์•ˆ์˜ ์ฝ˜ํ…์ธ ๋ฅผ ์„œ๋ฒ„๋ Œ๋”๋ง ๋™์•ˆ (์Šคํ”ผ๋„ˆ๋‚˜ ๊ธ€๋ฆฌ๋จธ๊ฐ™์€) loading fallbck์œผ๋กœ ๋Œ€์ฒด ํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

  • ๋˜๋Š” useLayoutEffect๊ฐ€ ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ hydration ์ดํ›„์—๋งŒ ๋ Œ๋”๋งํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ถˆ๋ฆฌ์–ธ ํƒ€์ž…์ธ isMounted state๋ฅผ ์ดˆ๊นƒ๊ฐ’์ธ false๋กœ ์œ ์ง€ํ•˜๋‹ค๊ฐ€, useEffect ํ˜ธ์ถœ๋˜๋ฉด ๊ฑฐ๊ธฐ์„œ true๋กœ ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜์„ธ์š”. ๊ทธ๋Ÿฌ๋ฉด ๋ Œ๋”๋ง ๋กœ์ง์€ return isMounted ? <RealContent /> : <FallbackContent /> ์ฒ˜๋Ÿผ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์„œ๋ฒ„์—์„œ ๋ Œ๋”๋งํ•˜๋Š” ์ค‘์ด๊ฑฐ๋‚˜ hydration ๋™์•ˆ ์‚ฌ์šฉ์ž๋Š” FallbackContent๋ฅผ ๋ณผ ๊ฒƒ์ด๊ณ  FallbackContent๋Š” useLayoutEffect๋ฅผ ํ˜ธ์ถœํ•˜์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ ํ›„์— React๊ฐ€ FallbackContent๋ฅผ ํด๋ผ์ด์–ธํŠธ ์ „์šฉ์ด๋ฉด์„œ useLayoutEffect๋ฅผ ํ˜ธ์ถœํ•˜๋Š” RealContent๋กœ ๋ณ€๊ฒฝํ•  ๊ฒ๋‹ˆ๋‹ค.

  • ์ปดํฌ๋„ŒํŠธ๋ฅผ ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ ์ €์žฅ์†Œ์™€ ๋™๊ธฐํ™”ํ•˜๊ณ , ๋ ˆ์ด์•„์›ƒ ๊ณ„์‚ฐ ์™ธ์— ๋‹ค๋ฅธ ์ด์œ ๋กœ useLayoutEffect์— ์˜์กดํ•˜๋Š” ๊ฒฝ์šฐ๋ผ๋ฉด, ๋Œ€์‹  useSyncExternalStore๋ฅผ ๊ณ ๋ คํ•ด ๋ณด์„ธ์š”. ์ด Hook์€ ์„œ๋ฒ„ ๋ Œ๋”๋ง์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.