๋ฐ˜์‘ํ˜•

useRef ํ•จ์ˆ˜๋Š” current ์†์„ฑ์„ ๊ฐ€์ง„ ref ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ref.current ๊ฐ’์„ HTMLElement์— ํ• ๋‹นํ•ด์„œ ํ•ด๋‹น ์š”์†Œ์— focusํ•˜๊ฑฐ๋‚˜, ์—˜๋ฆฌ๋จผํŠธ ํฌ๊ธฐ ๋“ฑ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. ref ๊ฐ์ฒด๋Š” ์•„๋ž˜ ํŠน์ง•์„ ๊ฐ–๋Š”๋‹ค.

 

  • ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋‹ค์‹œ ๋ Œ๋”๋ง ๋ผ๋„ ref.current ๊ฐ’์€ ๊ทธ๋Œ€๋กœ ์œ ์ง€ํ•œ๋‹ค.
  • ref.current ๊ฐ’์ด ๋ณ€๊ฒฝ๋ผ๋„ ์ปดํฌ๋„ŒํŠธ๋Š” ๋‹ค์‹œ ๋ Œ๋”๋งํ•˜์ง€ ์•Š๋Š”๋‹ค
  • HTMLElement ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ˆซ์ž / ๋ฌธ์ž์—ด / ๋ฐฐ์—ด ๋“ฑ ๊ฐ’์„ ํ• ๋‹นํ•  ์ˆ˜ ์žˆ๋‹ค.

 

Case 1 — ๋ฆฌ๋ Œ๋”๋ง์ด ํ•„์š” ์—†๋Š” ๊ฐ’์„ ๊ด€๋ฆฌํ•  ๋•Œ โญ๏ธ


useRef๋Š” .current ํ”„๋กœํผํ‹ฐ๋กœ ์ „๋‹ฌ๋œ ์ธ์ž(initialValue)๋กœ ์ดˆ๊ธฐํ™”๋œ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅํ•œ ref ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๋ฐ˜ํ™˜๋œ ๊ฐ์ฒด๋Š” ์ปดํฌ๋„ŒํŠธ์˜ ์ „ ์ƒ์• ์ฃผ๊ธฐ๋ฅผ ํ†ตํ•ด ์œ ์ง€๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค.
— React ๊ณต์‹ ๋ฌธ์„œ

 

๊ณต์‹ ๋ฌธ์„œ์— ๋”ฐ๋ฅด๋ฉด useRef ํ•จ์ˆ˜๊ฐ€ ๋ฐ˜ํ™˜ํ•œ ref ๊ฐ์ฒด๋Š” ์ปดํฌ๋„ŒํŠธ ์ƒ์• ์ฃผ๊ธฐ๋ฅผ ํ†ตํ•ด ์œ ์ง€๋œ๋‹ค๊ณ  ๋งํ•œ๋‹ค. ์—ฌ๊ธฐ์„œ ๋งํ•˜๋Š” ์ƒ์• ์ฃผ๊ธฐ๋Š” DOM์— ๋งˆ์šดํŠธ~์–ธ๋งˆ์šดํŠธ๊นŒ์ง€ ๊ณผ์ •์ด๋‹ค. ์ปดํฌ๋„ŒํŠธ๋Š” ๋ณ€๊ฒฝ๋œ props๋ฅผ ๋ฐ›๊ฑฐ๋‚˜, ์ž์‹ ์˜ state๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด ๋‹ค์‹œ ๋ Œ๋”๋งํ•˜๋Š”๋ฐ, ์ด๋•Œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋‹ค์‹œ ์‹คํ–‰๋˜๋ฉด์„œ const a = 1 ๊ฐ™์€ ๋ณ€์ˆ˜๋„ ์žฌ์„ ์–ธ/์žฌํ• ๋‹น ๋œ๋‹ค.

const a = 1; // ๋ Œ๋”๋งํ•  ๋•Œ๋งˆ๋‹ค ๊ฐ’ ์ดˆ๊ธฐํ™”
const [state, setState] = useState(1); // ๊ฐ’ ์œ ์ง€, ๊ฐ’ ๋ณ€๊ฒฝ ์‹œ ๋ฆฌ๋ Œ๋”๋ง
const ref = useRef(1); // ๊ฐ’ ์œ ์ง€, ๊ฐ’ ๋ณ€๊ฒฝ๋ผ๋„ ๋ฆฌ๋ Œ๋”๋ง ์•ˆํ•จ

 

์•„๋ž˜ 2๊ฐœ ๋ฒ„ํŠผ์ด ์žˆ๋‹ค. ์ƒ๋‹จ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•  ๋•Œ๋งˆ๋‹ค useState๋กœ ์ƒ์„ฑํ•œ state ๊ฐ’์„ 1์”ฉ ๋”ํ•˜๊ณ , ํ•˜๋‹จ ๋ฒ„ํŠผ๋„ ref.current ๊ฐ’์„ 1์”ฉ ๋”ํ•œ๋‹ค.

 

 

์ƒ๋‹จ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•ด์„œ state ๊ฐ’์ด ๋ณ€ํ•˜๋ฉด ๋ฆฌ๋ Œ๋”๋ง ๋ผ์„œ ํ™”๋ฉด์— ๋ฐ”๋กœ ์—…๋ฐ์ดํŠธ ๋œ๋‹ค. ๋ฐ˜๋ฉด ํ•˜๋‹จ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•ด์„œ ref.current ๊ฐ’์ด ๋ณ€ํ•ด๋„ ๋ฆฌ๋ Œ๋”๋ง์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ํ™”๋ฉด์—๋„ ์—…๋ฐ์ดํŠธ๋˜์ง€ ์•Š๋Š”๋‹ค. ๋Œ€์‹  ๋ณ€ํ•œ ๊ฐ’์€ ๊ณ„์† ์œ ์ง€ํ•˜๊ณ  ์žˆ๋‹ค.

 

ํ•˜๋‹จ ๋ฒ„ํŠผ์„ 3๋ฒˆ ๋ˆ„๋ฅด๋ฉด ref.current๊ฐ€ +3 ๋ผ์„œ ๋‚ด๋ถ€์ ์œผ๋กœ 4 ๊ฐ’์„ ๊ฐ–๊ณ (ํ™”๋ฉด์—” ์—…๋ฐ์ดํŠธ๋˜์ง€ ์•Š์Œ), ๋‹ค์‹œ ์ƒ๋‹จ ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ state ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜๋ฉด ๋ฆฌ๋ Œ๋”๋ง์ด ๋ฐœ์ƒํ•˜์—ฌ ๊ทธ์ œ์„œ์•ผ ๋ณ€๊ฒฝ๋œ ref.current ๊ฐ’์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

๋”๋ณด๊ธฐ
import React, { useRef, useState } from "react";

const initialState = 1;
const buttonStyle =
  "w-52 bg-blue-600 text-white p-2 rounded leading-none flex items-center justify-between active:bg-violet-500 focus:ring";
const spanStyle =
  "font-bold bg-white p-1 rounded text-blue-600 text-xs ml-2 select-none";

const UseRef = function () {
  const [state, setState] = useState(initialState); // ๊ฐ’ ์œ ์ง€, ๊ฐ’ ๋ณ€๊ฒฝ ์‹œ ๋ฆฌ๋ Œ๋”๋ง
  const ref = useRef(initialState); // ๊ฐ’ ์œ ์ง€, ๊ฐ’ ๋ณ€๊ฒฝ๋ผ๋„ ๋ฆฌ๋ Œ๋”๋ง ์•ˆํ•จ

  return (
    <div className="h-full flex flex-col justify-center items-center gap-2 text-lg text-neutral-700">
      <button
        className={buttonStyle}
        type="button"
        onClick={() => setState((prev) => prev + 1)}
      >
        STATE + 1 <span className={spanStyle}>{state}</span>
      </button>
      <button
        className={buttonStyle}
        type="button"
        onClick={() => {
          ref.current += 1;
          console.log("ref.current: ", ref.current);
        }}
      >
        REF.CURRENT + 1 <span className={spanStyle}>{ref.current}</span>
      </button>
    </div>
  );
};

export default UseRef;

 

์ด ๊ฐ™์€ ref ๊ฐ์ฒด ํŠน์„ฑ์„ ์ด์šฉํ•ด ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์—์„œ ๋ณ€๊ฒฝ๋œ ๊ฐ’์„ ๊ด€๋ฆฌํ•ด์•ผ ํ•˜์ง€๋งŒ, ํ™”๋ฉด์—” ์—…๋ฐ์ดํŠธ(๋ฆฌ๋ Œ๋”๋ง) ํ•  ํ•„์š” ์—†๋Š” ๊ฐ’์„ ๊ด€๋ฆฌํ•  ๋•Œ ์œ ์šฉํ•˜๊ฒŒ ์“ฐ์ธ๋‹ค.

 

 

Case 2 — callbackRef (DOM Node ํฌ๊ธฐ ๊ตฌํ•˜๊ธฐ) โญ๏ธ


useRef๋Š” ๋‚ด์šฉ์ด ๋ณ€๊ฒฝ๋  ๋•Œ ๊ทธ๊ฒƒ์„ ์•Œ๋ ค์ฃผ์ง€ ์•Š๋Š”๋‹ค๋Š” ๊ฒƒ์„ ์œ ๋…ํ•˜์„ธ์š”. .current ํ”„๋กœํผํ‹ฐ๊ฐ€ ๋ณ€๊ฒฝ๋ผ๋„ ๋ฆฌ๋ Œ๋”๋ง์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ React๊ฐ€ DOM ๋…ธ๋“œ์— ref๋ฅผ attachํ•˜๊ฑฐ๋‚˜ detach ํ•  ๋•Œ ์–ด๋–ค ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์ฝœ๋ฐฑ ref๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.
— React ๊ณต์‹ ๋ฌธ์„œ(์˜ ๋ฌธ๋งฅ ์กฐ๊ธˆ ์ˆ˜์ •)

 

ref.current ๊ฐ’์„ ๋ณ€๊ฒฝํ•ด๋„ ๋ฆฌ๋ Œ๋”๋ง์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค. ์ฆ‰, DOM ๋…ธ๋“œ์˜ ref ์†์„ฑ์— ref.current ๊ฐ’์„ ํ• ๋‹นํ•˜๊ฑฐ๋‚˜(attach) ํ• ๋‹น์„ ํ•ด์ œํ•ด๋„(detach) ์ปดํฌ๋„ŒํŠธ๋Š” ์ด๋ฅผ ์ธ์ง€ํ•˜์ง€ ๋ชปํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค.

 

์•„๋ž˜ ์ฝ”๋“œ์—์„œ wrapperRef.current ๊ฐ’์ด null์—์„œ HTMLDivElement๋กœ ๋ณ€๊ฒฝ๋ผ๋„ ์ปดํฌ๋„ŒํŠธ๋Š” ์ด๋ฅผ ์ธ์ง€ํ•˜์ง€ ๋ชปํ•œ๋‹ค. ๋•Œ๋ฌธ์— console.log(wrapperRef.current)๋ฅผ ์ฐ์–ด๋ด๋„ null๋กœ ๋‚˜์˜จ๋‹ค.

import React, { useRef } from "react";

const App = function () {
  const wrapperRef = useRef(null);
  console.log(wrapperRef.current); // null

  return <div ref={wrapperRef}></div>;
};

 

ref ๊ฐ์ฒด๊ฐ€ DOM Node์— attach ํ˜น์€ detach๋  ๋•Œ ํŠน์ • ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด callback ref ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค. DOM Node์˜ ref ์†์„ฑ์— ref ๊ฐ์ฒด๊ฐ€ ์•„๋‹Œ ํ•จ์ˆ˜๋ฅผ ๋„˜๊ธฐ๋Š” ๋ฐฉ์‹์ด๋‹ค. DOM Node์˜ ๊ฐ€๋กœ/์„ธ๋กœ ๊ฐ’์„ ์•Œ์•„์•ผ ํ•  ๋•Œ ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

๋”๋ณด๊ธฐ

๐Ÿ’ก node.offsetWidth, node.offsetHeight ๊ฐ’์€ getBoundingClientRect ๋ฉ”์„œ๋“œ๋กœ ์กฐํšŒํ•œ width, height ๊ฐ’๊ณผ ๋™์ผํ•˜๋‹ค. ๋‹จ, offsetWidth, offsetHeight๋Š” ์†Œ์ˆ˜์ ์„ ์ •์ˆ˜๋กœ ๋ฐ˜์˜ฌ๋ฆผํ•œ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค. (์ฐธ๊ณ ๋งํฌ)

 

import React, { useCallback, useState } from "react";
import CatImg from "../../assets/cat.png";

const CallbackRef = function () {
  const [size, setSize] = useState({ height: 0, width: 0 });
  const [loading, setLoading] = useState(true);

  const callbackRef = useCallback(
    (node) => {
      if (node && !loading) {
        const { width, height } = node.getBoundingClientRect();
        setSize({ width, height });
      }
    },
    [loading],
  );

  return (
    <div className="h-full flex flex-col justify-center items-center gap-8">
      <p className="text-lg text-neutral-700">
        HEIGHT : {size.height} / WIDTH : {size.width}
      </p>
      <div ref={callbackRef}>
        <img
          className="max-w-[13rem]"
          src={CatImg} // src={require(../assets/cat.png)}
          alt="cat"
          onLoad={() => setLoading(false)}
        />
      </div>
    </div>
  );
};

export default CallbackRef;
// useCallback์„ ์‚ฌ์šฉํ•œ ์˜ˆ์‹œ
const callbackRef = useCallback((node) => {
  if (node) {
    /* ... */
  }
}, []);

return (
  <div ref={callbackRef}>
    <img />
  </div>
);
// useState๋ฅผ ์‚ฌ์šฉํ•œ ์˜ˆ์‹œ
const [callbackRef, setCallbackRef] = useState(null);

useEffect(() => {
  if (callbackRef) {
    /* ... */
  }
}, [callbackRef]);

return (
  <div ref={callbackRef}>
    <img />
  </div>
);

 

callbackRef ํ•จ์ˆ˜๋ฅผ ์ด๋ฏธ์ง€ wrapper์ธ div ํƒœ๊ทธ์˜ ref ์†์„ฑ์— ํ• ๋‹นํ•ด์„œ height/width ๊ฐ’์„ ๊ตฌํ–ˆ๋‹ค

 

Case 3 — forwardRef


props๋ฅผ ์ „๋‹ฌํ•˜๋“ฏ ์ž์‹ ์ปดํฌ๋„ŒํŠธ์—๊ฒŒ ref๋ฅผ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋‹ค. ๋‹ค๋งŒ ref๋Š” props ๊ฐ์ฒด์˜ ํ”„๋กœํผํ‹ฐ๋กœ ๋“ค์–ด๊ฐ€์ง€ ์•Š๊ณ , ref๋ฅผ ๋„˜๊ฒจ ๋ฐ›๋Š” ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ๋ฅผ forwardRef ํ•จ์ˆ˜๋กœ ๊ฐ์‹ธ์ฃผ๋Š” ์ž‘์—…์ด ํ•„์š”ํ•˜๋‹ค. ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์—์„œ ์ž์‹ ์ปดํฌ๋„ŒํŠธ์˜ ref ์ƒํƒœ๊ฐ’์„ ๊ด€๋ฆฌํ•ด์•ผํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.

๋”๋ณด๊ธฐ
import React from "react";
import { RandomCornerStyle, RandomWidthHeight } from "../../utils/webUtils";

const randomAxis = RandomWidthHeight(300, 120);
const randomCorner = RandomCornerStyle();

const Circle = React.forwardRef((props, ref) => {
  return (
    <div
      className={`bg-indigo-500 ${randomCorner}`}
      style={{
        width: randomAxis.width,
        height: randomAxis.height,
      }}
      ref={ref}
    />
  );
});
Circle.displayName = "Circle"; // ESLint: react/display-name ์—๋Ÿฌ ๋Œ€์‘

const ForwardRef = function () {
  const circleRef = React.useRef(null);
  const [circleSize, setCircleSize] = React.useState({ width: 0, height: 0 });

  React.useEffect(() => {
    const { width, height } = circleRef.current.getBoundingClientRect();
    setCircleSize({ width, height });
  }, []);

  return (
    <div className="h-full flex flex-col justify-center items-center gap-8">
      <p className="text-lg text-neutral-700">
        WIDTH : {circleSize.width} / HEIGHT : {circleSize.height}
      </p>
      <Circle ref={circleRef} />
    </div>
  );
};

export default ForwardRef;
// ์ž์‹ ์ปดํฌ๋„ŒํŠธ
const randomAxis = RandomWidthHeight(300, 120); // parameter: max, min

const Circle = React.forwardRef((props, ref) => {
  return (
    <div
      style={{
        width: randomAxis.width,
        height: randomAxis.height,
      }}
      ref={ref}
    />
  );
});
Circle.displayName = "Circle"; // ESLint: react/display-name ์—๋Ÿฌ ๋Œ€์‘

// ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ
const App = function () {
  const circleRef = React.useRef(null);
  // ...
  return <Circle ref={circleRef} />;
};

 

์œ„ ์ฝ”๋“œ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ <Circle /> ์ปดํฌ๋„ŒํŠธ๋Š” ๋ Œ๋”๋งํ•  ๋•Œ๋งˆ๋‹ค ๋žœ๋คํ•œ width, height ๊ฐ’์„ ๊ฐ–๋Š”๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณธ๋‹ค. ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๋Š” ์ž์‹ ์ปดํฌ๋„ŒํŠธ์˜ ๋žœ๋คํ•œ width, height ๊ฐ’์„ ์•Œ์•„์•ผ ํ•˜๋Š” ์ƒํ™ฉ์ด๋‹ค.

 

์ž์‹ ์ปดํฌ๋„ŒํŠธ์—๊ฒŒ ref ๊ฐ์ฒด๋ฅผ ๋„˜๊ธด ํ›„, ref ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด ์กฐํšŒํ•œ width/height ๊ฐ’์„ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์—์„œ ํ‘œ์‹œํ•˜๊ณ  ์žˆ๋‹ค

 

  1. ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์—์„œ ref๋ฅผ ๋„˜๊ธธ ์‹œ์ ์—” ์•„์ง ์ž์‹ ์ปดํฌ๋„ŒํŠธ์˜ width, height ๊ฐ’์„ ๋ชจ๋ฅด๋Š” ์ƒํƒœ๋‹ค.
  2. ์ž์‹ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋žœ๋คํ•œ width, height๋ฅผ ๊ฐ€์ง€๊ณ , ์ „๋‹ฌ๋ฐ›์€ ref๋ฅผ attachํ•ด์„œ ๋ Œ๋”๋งํ•˜๋ฉด,
  3. ๋งˆ์šดํŠธ๋ฅผ ์™„๋ฃŒํ–ˆ์œผ๋ฏ€๋กœ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์— ์žˆ๋Š” useEffect๊ฐ€ ์‹คํ–‰๋œ๋‹ค. useEffect๊ฐ€ ์‹คํ–‰๋  ์‹œ์ ์—” ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋กœ ๋„˜๊ฒผ๋˜ ref ๊ฐ์ฒด(circleRef)๋ฅผ ํ†ตํ•ด, width, height ๊ฐ’์„ ์ฝ์„ ์ˆ˜ ์žˆ๋‹ค.

 

// ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ
const [circleSize, setCircleSize] = React.useState({ width: 0, height: 0 });
const circleRef = React.useRef(null); // null | HTMLDivElement

React.useEffect(() => {
  const { width, height } = circleRef.current.getBoundingClientRect();
  setCircleSize({ width, height });
}, []);

 

๋ ˆํผ๋Ÿฐ์Šค


 

React useRef์˜ ๋‹ค์–‘ํ•œ ํ™œ์šฉ ๋ฐฉ๋ฒ•(mutable object, callback ref์™€ forwardRef) - ์ดํ™”๋ž‘ ๋ธ”๋กœ๊ทธ

React useRef์˜ ๋‹ค์–‘ํ•œ ํ™œ์šฉ ๋ฐฉ๋ฒ•(mutable object, callback ref์™€ forwardRef) ๋ฆฌ์•กํŠธ์—์„œ render() ๋ฉ”์„œ๋“œ์— ์˜ํ•ด ๋งŒ๋“ค์–ด์ง€๋Š” DOM์— ์ ‘๊ทผํ•˜๋Š” ๋ฐฉ์‹ ์œผ๋กœ ref ๋ฅผ ์ œ๊ณตํ•œ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋ฐฐ์†ก์ง€ ์ •๋ณด๋ฅผ ์ž…๋ ฅ ๋ฐ›์•„์•ผ ํ•˜

leehwarang.github.io

 


๊ธ€ ์ˆ˜์ •์‚ฌํ•ญ์€ ๋…ธ์…˜ ํŽ˜์ด์ง€์— ๊ฐ€์žฅ ๋น ๋ฅด๊ฒŒ ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค. ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ•ด ์ฃผ์„ธ์š”
๋ฐ˜์‘ํ˜•