Ref๋กœ ๊ฐ’ ์ฐธ์กฐํ•˜๊ธฐ

์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ผ๋ถ€ ์ •๋ณด๋ฅผ โ€œ๊ธฐ์–ตโ€ํ•˜๊ณ  ์‹ถ์ง€๋งŒ, ํ•ด๋‹น ์ •๋ณด๊ฐ€ ๋ Œ๋”๋ง์„ ์œ ๋ฐœํ•˜์ง€ ์•Š๋„๋ก ํ•˜๋ ค๋ฉด Ref๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.

ํ•™์Šต ๋‚ด์šฉ

  • ์ปดํฌ๋„ŒํŠธ Ref๋ฅผ ์–ด๋–ป๊ฒŒ ์ถ”๊ฐ€ํ•˜๋Š”๊ฐ€
  • Ref์˜ ๊ฐ’์ด ์–ด๋–ป๊ฒŒ ์—…๋ฐ์ดํŠธ๋˜๋Š”๊ฐ€
  • Ref๊ฐ€ State์™€ ์–ด๋–ป๊ฒŒ ๋‹ค๋ฅธ๊ฐ€
  • Ref๋ฅผ ์–ด๋–ป๊ฒŒ ์•ˆ์ „ํ•˜๊ฒŒ ์‚ฌ์šฉํ• ๊นŒ

์ปดํฌ๋„ŒํŠธ์— Ref๋ฅผ ์ถ”๊ฐ€ํ•˜๊ธฐ

React์—์„œ useRef Hook์„ ๊ฐ€์ ธ์™€ ์ปดํฌ๋„ŒํŠธ์— Ref๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import { useRef } from 'react';

์ปดํฌ๋„ŒํŠธ ๋‚ด์—์„œ useRef Hook์„ ํ˜ธ์ถœํ•˜๊ณ  ์ฐธ์กฐํ•  ์ดˆ๊นƒ๊ฐ’์„ ์œ ์ผํ•œ ์ธ์ž๋กœ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ์€ ๊ฐ’ 0์— ๋Œ€ํ•œ Ref์ž…๋‹ˆ๋‹ค.

const ref = useRef(0);

useRef๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

{
current: 0 // useRef์— ์ „๋‹ฌํ•œ ๊ฐ’
}
An arrow with 'current' written on it stuffed into a pocket with 'ref' written on it.

Illustrated by Rachel Lee Nabors

ref.current ํ”„๋กœํผํ‹ฐ๋ฅผ ํ†ตํ•ด ํ•ด๋‹น Ref์˜ current ๊ฐ’์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ฐ’์€ ์˜๋„์ ์œผ๋กœ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ฝ๊ณ  ์“ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. React๊ฐ€ ์ถ”์ ํ•˜์ง€ ์•Š๋Š” ๊ตฌ์„ฑ ์š”์†Œ์˜ ๋น„๋ฐ€ ์ฃผ๋จธ๋‹ˆ๋ผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (์ด๊ฒƒ์ด ๋ฐ”๋กœ React์˜ ๋‹จ๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ํ๋ฆ„์—์„œ โ€œํƒˆ์ถœ๊ตฌโ€๊ฐ€ ๋˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์•„๋ž˜์—์„œ ์ž์„ธํžˆ ์„ค๋ช…ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค!)

์—ฌ๊ธฐ์„œ ๋ฒ„ํŠผ์€ ํด๋ฆญํ•  ๋•Œ๋งˆ๋‹ค ref.current๋ฅผ ์ฆ๊ฐ€์‹œํ‚ต๋‹ˆ๋‹ค.

import { useRef } from 'react';

export default function Counter() {
  let ref = useRef(0);

  function handleClick() {
    ref.current = ref.current + 1;
    alert('You clicked ' + ref.current + ' times!');
  }

  return (
    <button onClick={handleClick}>
      Click me!
    </button>
  );
}

Ref๋Š” ์ˆซ์ž๋ฅผ ๊ฐ€๋ฆฌํ‚ค์ง€๋งŒ, State์ฒ˜๋Ÿผ ๋ฌธ์ž์—ด, ๊ฐ์ฒด, ์‹ฌ์ง€์–ด ํ•จ์ˆ˜ ๋“ฑ ๋ชจ๋“  ๊ฒƒ์„ ๊ฐ€๋ฆฌํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. State์™€ ๋‹ฌ๋ฆฌ Ref๋Š” ์ฝ๊ณ  ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋Š” current ํ”„๋กœํผํ‹ฐ๋ฅผ ๊ฐ€์ง„ ์ผ๋ฐ˜ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค.

์ปดํฌ๋„ŒํŠธ๋Š” ๋ชจ๋“  ์ฆ๊ฐ€์— ๋Œ€ํ•˜์—ฌ ๋‹ค์‹œ ๋ Œ๋”๋ง ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. State์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ Ref๋„ React์— ๋ฆฌ๋ Œ๋”์— ์˜ํ•ด ์œ ์ง€๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜, State๋ฅผ ์„ค์ •ํ•˜๋ฉด ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋‹ค์‹œ ๋ Œ๋”๋ง ๋ฉ๋‹ˆ๋‹ค. Ref๋ฅผ ๋ณ€๊ฒฝํ•˜๋ฉด ๋‹ค์‹œ ๋ Œ๋”๋ง ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค!

์˜ˆ์‹œ: ์Šคํ†ฑ์›Œ์น˜ ์ž‘์„ฑํ•˜๊ธฐ

Ref์™€ State๋ฅผ ๋‹จ์ผ ์ปดํฌ๋„ŒํŠธ๋กœ ๊ฒฐํ•ฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์‚ฌ์šฉ์ž๊ฐ€ ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ ์‹œ์ž‘ํ•˜๊ฑฐ๋‚˜ ์ค‘์ง€ํ•  ์ˆ˜ ์žˆ๋Š” ์Šคํ†ฑ์›Œ์น˜๋ฅผ ๋งŒ๋“ค์–ด๋ด…์‹œ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ โ€œ์‹œ์ž‘โ€์„ ๋ˆ„๋ฅธ ํ›„ ์‹œ๊ฐ„์ด ์–ผ๋งˆ๋‚˜ ์ง€๋‚ฌ๋Š”์ง€ ํ‘œ์‹œํ•˜๋ ค๋ฉด ์‹œ์ž‘ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅธ ์‹œ๊ธฐ์™€ ํ˜„์žฌ ์‹œ๊ฐ์„ ์ถ”์ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ์ •๋ณด๋Š” ๋ Œ๋”๋ง์— ์‚ฌ์šฉ๋˜๋ฏ€๋กœ State๋ฅผ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.

const [startTime, setStartTime] = useState(null);
const [now, setNow] = useState(null);

์‚ฌ์šฉ์ž๊ฐ€ โ€œ์‹œ์ž‘โ€์„ ๋ˆ„๋ฅด๋ฉด setInterval์„ ์‚ฌ์šฉํ•˜์—ฌ 10๋ฐ€๋ฆฌ์ดˆ๋งˆ๋‹ค ์‹œ๊ฐ„์„ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.

import { useState } from 'react';

export default function Stopwatch() {
  const [startTime, setStartTime] = useState(null);
  const [now, setNow] = useState(null);

  function handleStart() {
    // ์นด์šดํŒ…์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.
    setStartTime(Date.now());
    setNow(Date.now());

    setInterval(() => {
      // 10ms ๋งˆ๋‹ค ํ˜„์žฌ ์‹œ๊ฐ„์„ ์—…๋ฐ์ดํŠธ ํ•ฉ๋‹ˆ๋‹ค.
      setNow(Date.now());
    }, 10);
  }

  let secondsPassed = 0;
  if (startTime != null && now != null) {
    secondsPassed = (now - startTime) / 1000;
  }

  return (
    <>
      <h1>Time passed: {secondsPassed.toFixed(3)}</h1>
      <button onClick={handleStart}>
        Start
      </button>
    </>
  );
}

โ€Stopโ€ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด now State ๋ณ€์ˆ˜์˜ ์—…๋ฐ์ดํŠธ๋ฅผ ์ค‘์ง€ํ•˜๊ธฐ ์œ„ํ•ด ๊ธฐ์กด Interval์„ ์ทจ์†Œํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด clearInterval์„ ํ˜ธ์ถœํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด์ „์— ์‚ฌ์šฉ์ž๊ฐ€ ์‹œ์ž‘์„ ๋ˆŒ๋ €์„ ๋•Œ setInterval ํ˜ธ์ถœ๋กœ ๋ฐ˜ํ™˜๋œ interval ID๋ฅผ ์ œ๊ณตํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Interval ID๋Š” ์–ด๋”˜๊ฐ€์— ๋ณด๊ด€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Interval ID๋Š” ๋ Œ๋”๋ง์— ์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ Ref์— ์ €์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import { useState, useRef } from 'react';

export default function Stopwatch() {
  const [startTime, setStartTime] = useState(null);
  const [now, setNow] = useState(null);
  const intervalRef = useRef(null);

  function handleStart() {
    setStartTime(Date.now());
    setNow(Date.now());

    clearInterval(intervalRef.current);
    intervalRef.current = setInterval(() => {
      setNow(Date.now());
    }, 10);
  }

  function handleStop() {
    clearInterval(intervalRef.current);
  }

  let secondsPassed = 0;
  if (startTime != null && now != null) {
    secondsPassed = (now - startTime) / 1000;
  }

  return (
    <>
      <h1>Time passed: {secondsPassed.toFixed(3)}</h1>
      <button onClick={handleStart}>
        Start
      </button>
      <button onClick={handleStop}>
        Stop
      </button>
    </>
  );
}

๋ Œ๋”๋ง์— ์ •๋ณด๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ํ•ด๋‹น ์ •๋ณด๋ฅผ State๋กœ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์—๊ฒŒ๋งŒ ํ•„์š”ํ•œ ์ •๋ณด์ด๊ณ  ๋ณ€๊ฒฝ์ด ์ผ์–ด๋‚  ๋•Œ ๋ฆฌ๋ Œ๋”๋ง์ด ํ•„์š”ํ•˜์ง€ ์•Š๋‹ค๋ฉด, Ref๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋” ํšจ์œจ์ ์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Ref์™€ State์˜ ์ฐจ์ด

Ref๊ฐ€ State๋ณด๋‹ค ๋œ โ€œ์—„๊ฒฉํ•œโ€ ๊ฒƒ์œผ๋กœ ์ƒ๊ฐ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ํ•ญ์ƒ State ์„ค์ • ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋Œ€๋ถ€๋ถ„์€ State๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. Ref๋Š” ์ž์ฃผ ํ•„์š”ํ•˜์ง€ ์•Š์€ โ€œํƒˆ์ถœ๊ตฌโ€์ž…๋‹ˆ๋‹ค. State์™€ Ref๋ฅผ ๋น„๊ตํ•œ ๊ฒƒ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

RefState
useRef(initialValue) ๋Š” { current: initialValue }๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.useState(initialValue)๋Š” State ๋ณ€์ˆ˜์˜ ํ˜„์žฌ ๊ฐ’๊ณผ Setter ํ•จ์ˆ˜ [value, setValue]๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
current ๊ฐ’์„ ๋ฐ”๊ฟ”๋„ ๋ฆฌ๋ Œ๋”๋ง ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.State๋ฅผ ๋ฐ”๊พธ๋ฉด ๋ฆฌ๋ Œ๋”๋ง ํ•ฉ๋‹ˆ๋‹ค.
Mutable: ๋ Œ๋”๋ง ํ”„๋กœ์„ธ์Šค ์™ธ๋ถ€์—์„œ current ๊ฐ’์„ ์ˆ˜์ • ๋ฐ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.Immutable: State๋ฅผ ์ˆ˜์ •ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” State ์„ค์ • ํ•จ์ˆ˜๋ฅผ ๋ฐ˜๋“œ์‹œ ์‚ฌ์šฉํ•˜์—ฌ ๋ฆฌ๋ Œ๋”๋ง ๋Œ€๊ธฐ์—ด์— ๋„ฃ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
๋ Œ๋”๋ง ์ค‘์—๋Š” current ๊ฐ’์„ ์ฝ๊ฑฐ๋‚˜ ์“ฐ๋ฉด ์•ˆ ๋ฉ๋‹ˆ๋‹ค.์–ธ์ œ๋“ ์ง€ State๋ฅผ ์ฝ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๊ฐ ๋ Œ๋”๋ง๋งˆ๋‹ค ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋Š” ์ž์ฒด์ ์ธ State์˜ Snapshot์ด ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ์€ State์™€ ํ•จ๊ป˜ ๊ตฌํ˜„ํ•œ ์นด์šดํ„ฐ ๋ฒ„ํŠผ์ž…๋‹ˆ๋‹ค.

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
  }

  return (
    <button onClick={handleClick}>
      You clicked {count} times
    </button>
  );
}

count ๊ฐ’์„ ํ‘œ์‹œํ•˜๋ฏ€๋กœ State ๊ฐ’์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ํƒ€๋‹นํ•ฉ๋‹ˆ๋‹ค. ์นด์šดํ„ฐ์˜ ๊ฐ’์„ setCount()๋กœ ์„ค์ •ํ•˜๋ฉด React๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‹ค์‹œ ๋ Œ๋”๋งํ•˜๊ณ  ์ƒˆ ์นด์šดํŠธ๋ฅผ ๋ฐ˜์˜ํ•˜๋„๋ก ํ™”๋ฉด์„ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.

์ด๋ฅผ Ref์™€ ํ•จ๊ป˜ ๊ตฌํ˜„ํ•˜๋ ค๊ณ  ํ•˜๋ฉด React๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‹ค์‹œ ๋ Œ๋”๋งํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์นด์šดํŠธ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค! ์ด ๋ฒ„ํŠผ์„ ํด๋ฆญํ•ด๋„ ํ…์ŠคํŠธ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜์ง€ ์•Š๋Š” ๋ฐฉ๋ฒ•์„ ํ™•์ธํ•ด๋ด…์‹œ๋‹ค.

import { useRef } from 'react';

export default function Counter() {
  let countRef = useRef(0);

  function handleClick() {
    // ์ด๊ฒƒ์€ ์ปดํฌ๋„ŒํŠธ์˜ ๋ฆฌ๋ Œ๋”๋ฅผ ์ผ์œผํ‚ค์ง€ ์•Š์Šต๋‹ˆ๋‹ค!
    countRef.current = countRef.current + 1;
  }

  return (
    <button onClick={handleClick}>
      You clicked {countRef.current} times
    </button>
  );
}

์ด๊ฒƒ์ด ๋ Œ๋”๋ง ์ค‘์— ref.current๋ฅผ ์ถœ๋ ฅํ•˜๋ฉด ์‹ ๋ขฐํ•  ์ˆ˜ ์—†๋Š” ์ฝ”๋“œ๊ฐ€ ๋‚˜์˜ค๋Š” ์ด์œ ์ž…๋‹ˆ๋‹ค. ์ด ๋ถ€๋ถ„์ด ํ•„์š”ํ•˜๋ฉด State๋ฅผ ๋Œ€์‹  ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ž์„ธํžˆ ์‚ดํŽด๋ณด๊ธฐ

useRef๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋‚˜์š”?

React๊ฐ€ useState์™€ useRef๋ฅผ ๋ชจ๋‘ ์ œ๊ณตํ•˜์ง€๋งŒ, ์›์น™์ ์œผ๋กœ useRef๋Š” useState ์œ„์— ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. React ๋‚ด๋ถ€์—์„œ useRef๋ฅผ ์ด๋ ‡๊ฒŒ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์„ ์ƒ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// Inside of React
function useRef(initialValue) {
const [ref, unused] = useState({ current: initialValue });
return ref;
}

์ฒซ ๋ฒˆ์งธ ๋ Œ๋”๋ง ์ค‘์— useRef๋Š” { current: initialValue }๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ฐ์ฒด๋Š” React์— ์˜ํ•ด ์ €์žฅ๋˜๋ฏ€๋กœ ๋‹ค์Œ ๋ Œ๋”๋ง ์ค‘์— ๊ฐ™์€ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ด ์˜ˆ์‹œ์—์„œ๋Š” State Setter๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ์ ์— ์ฃผ๋ชฉํ•˜์„ธ์š”. useRef๋Š” ํ•ญ์ƒ ๋™์ผํ•œ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•˜๋ฏ€๋กœ ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค!

React๋Š” useRef๊ฐ€ ์‹ค์ œ๋กœ ์ถฉ๋ถ„ํžˆ ์ผ๋ฐ˜์ ์ด๊ธฐ ๋•Œ๋ฌธ์— built-in ๋ฒ„์ „์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. setter๊ฐ€ ์—†๋Š” ์ผ๋ฐ˜์ ์ธ state ๋ณ€์ˆ˜๋ผ๊ณ  ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ์ฒด ์ง€ํ–ฅ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์— ์ต์ˆ™ํ•˜๋‹ค๋ฉด Ref๋Š” ์ธ์Šคํ„ด์Šค ํ•„๋“œ๋ฅผ ์ƒ๊ธฐ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ this.something ๋Œ€์‹ ์— somethingRef.current ์ฒ˜๋Ÿผ ์จ์•ผํ•ฉ๋‹ˆ๋‹ค.

Ref๋ฅผ ์‚ฌ์šฉํ•  ์‹œ๊ธฐ

์ผ๋ฐ˜์ ์œผ๋กœ ์ปดํฌ๋„ŒํŠธ๊ฐ€ React๋ฅผ โ€œ์™ธ๋ถ€โ€์™€ ์™ธ๋ถ€ APIโ€”์ปดํฌ๋„ŒํŠธ์˜ ํ˜•ํƒœ์— ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š๋Š” ๋ธŒ๋ผ์šฐ์ € API ์™€ ํ†ต์‹ ํ•ด์•ผ ํ•  ๋•Œ Ref๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ์€ ๋ช‡ ๊ฐ€์ง€ ํŠน๋ณ„ํ•œ ์ƒํ™ฉ์ž…๋‹ˆ๋‹ค.

์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ผ๋ถ€ ๊ฐ’์„ ์ €์žฅํ•ด์•ผ ํ•˜์ง€๋งŒ ๋ Œ๋”๋ง ๋กœ์ง์— ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ, Ref๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.

Ref์˜ ์ข‹์€ ์˜ˆ์‹œ

๋‹ค์Œ ์›์น™์„ ๋”ฐ๋ฅด๋ฉด ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ณด๋‹ค ์‰ฝ๊ฒŒ ์˜ˆ์ธกํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • Ref๋ฅผ ํƒˆ์ถœ๊ตฌ๋กœ ๊ฐ„์ฃผํ•ฉ๋‹ˆ๋‹ค. Ref๋Š” ์™ธ๋ถ€ ์‹œ์Šคํ…œ์ด๋‚˜ ๋ธŒ๋ผ์šฐ์ € API๋กœ ์ž‘์—…ํ•  ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋กœ์ง๊ณผ ๋ฐ์ดํ„ฐ ํ๋ฆ„์˜ ์ƒ๋‹น ๋ถ€๋ถ„์ด Ref์— ์˜์กดํ•œ๋‹ค๋ฉด ์ ‘๊ทผ ๋ฐฉ์‹์„ ์žฌ๊ณ ํ•ด ๋ณด๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
  • ๋ Œ๋”๋ง ์ค‘์— ref.current๋ฅผ ์ฝ๊ฑฐ๋‚˜ ์“ฐ์ง€ ๋งˆ์„ธ์š”. ๋ Œ๋”๋ง ์ค‘์— ์ผ๋ถ€ ์ •๋ณด๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ State๋ฅผ ๋Œ€์‹  ์‚ฌ์šฉํ•˜์„ธ์š”. ref.current๊ฐ€ ์–ธ์ œ ๋ณ€ํ•˜๋Š”์ง€ React๋Š” ๋ชจ๋ฅด๊ธฐ ๋•Œ๋ฌธ์— ๋ Œ๋”๋งํ•  ๋•Œ ์ฝ์–ด๋„ ์ปดํฌ๋„ŒํŠธ์˜ ๋™์ž‘์„ ์˜ˆ์ธกํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค. (if (!ref.current) ref.current = new Thing()๊ณผ ๊ฐ™์€ ์ฝ”๋“œ๋Š” ์ฒซ ๋ฒˆ์งธ ๋ Œ๋”๋ง ์ค‘์— Ref๋ฅผ ํ•œ ๋ฒˆ๋งŒ ์„ค์ •ํ•˜๋Š” ๊ฒฝ์šฐ๋ผ ์˜ˆ์™ธ์ž…๋‹ˆ๋‹ค.)

React State์˜ ์ œํ•œ์€ Ref์— ์ ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด State๋Š” ๋ชจ๋“  ๋ Œ๋”๋ง์— ๋Œ€ํ•œ Snapshot ๋ฐ ๋™๊ธฐ์ ์œผ๋กœ ์—…๋ฐ์ดํŠธ๋˜์ง€ ์•Š๋Š” ๊ฒƒ๊ณผ ๊ฐ™์ด ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ Ref์˜ current ๊ฐ’์„ ๋ณ€์กฐํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ฆ‰์‹œ ๋ณ€๊ฒฝ๋ฉ๋‹ˆ๋‹ค.

ref.current = 5;
console.log(ref.current); // 5

๊ทธ ์ด์œ ๋Š” Ref ์ž์ฒด๊ฐ€ ์ผ๋ฐ˜ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๊ฐ์ฒด์ฒ˜๋Ÿผ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

๋˜ํ•œ Ref๋กœ ์ž‘์—…ํ•  ๋•Œ Mutation ๋ฐฉ์ง€์— ๋Œ€ํ•ด ๊ฑฑ์ •ํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋ณ€ํ˜•ํ•˜๋Š” ๊ฐ์ฒด๋ฅผ ๋ Œ๋”๋ง์— ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ํ•œ, React๋Š” Ref ํ˜น์€ ํ•ด๋‹น ์ฝ˜ํ…์ธ ๋ฅผ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋“  ์‹ ๊ฒฝ ์“ฐ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

Ref์™€ DOM

์ž„์˜์˜ ๊ฐ’์„ Ref๋กœ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ Ref์˜ ๊ฐ€์žฅ ์ผ๋ฐ˜์ ์ธ ์‚ฌ์šฉ ์‚ฌ๋ก€๋Š” DOM ์—˜๋ฆฌ๋จผํŠธ์— ์ ‘๊ทผํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ฐฉ์‹์œผ๋กœ ์ž…๋ ฅ์ฐฝ์— ์ดˆ์ ์„ ๋งž์ถ”๋ ค๋Š” ๊ฒฝ์šฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. <div ref={myRef}>์™€ ๊ฐ™์€ JSX์˜ ref ์–ดํŠธ๋ฆฌ๋ทฐํŠธ์— Ref๋ฅผ ์ „๋‹ฌํ•˜๋ฉด React๋Š” ํ•ด๋‹น DOM ์—˜๋ฆฌ๋จผํŠธ๋ฅผ myRef.current์— ๋„ฃ์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์—˜๋ฆฌ๋จผํŠธ๊ฐ€ DOM ์—์„œ ์‚ฌ๋ผ์ง€๋ฉด, React ๋Š” myRef.current ๊ฐ’์„ null ๋กœ ์—…๋ฐ์ดํŠธ ํ•ฉ๋‹ˆ๋‹ค. ์ด์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ Ref๋กœ DOM ์กฐ์ž‘ํ•˜๊ธฐ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์š”์•ฝ

  • Ref๋Š” ๋ Œ๋”๋ง์— ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ๊ฐ’์„ ๊ณ ์ •ํ•˜๊ธฐ ์œ„ํ•œ ํƒˆ์ถœ๊ตฌ์ด๋ฉฐ, ์ž์ฃผ ํ•„์š”ํ•˜์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค.
  • Ref๋Š” ์ฝ๊ฑฐ๋‚˜ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋Š” current๋ผ๋Š” ํ”„๋กœํผํ‹ฐ๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋Š” ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์ˆœ์ˆ˜๊ฐ์ฒด์ž…๋‹ˆ๋‹ค.
  • useRef Hook์„ ํ˜ธ์ถœํ•ด Ref๋ฅผ ๋‹ฌ๋ผ๊ณ  React์— ์š”์ฒญํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • State์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ Ref๋Š” ์ปดํฌ๋„ŒํŠธ์˜ ๋ Œ๋”๋ง ๊ฐ„์— ์ •๋ณด๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • State์™€ ๋‹ฌ๋ฆฌ Ref์˜ current ๊ฐ’์„ ์„ค์ •ํ•˜๋ฉด ๋ฆฌ๋ Œ๋”๋ง์„ ํŠธ๋ฆฌ๊ฑฐํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ๋ Œ๋”๋ง ์ค‘์— ref.current๋ฅผ ์ฝ๊ฑฐ๋‚˜ ์“ฐ์ง€ ๋งˆ์„ธ์š”. ์ปดํฌ๋„ŒํŠธ๋ฅผ ์˜ˆ์ธกํ•˜๊ธฐ ์–ด๋ ต๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

์ฑŒ๋ฆฐ์ง€ 1 of 4:
์ •์ƒ์ ์œผ๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š๋Š” ์ฑ„ํŒ… ์ž…๋ ฅ์ฐฝ ์ˆ˜์ •

๋ฉ”์‹œ์ง€๋ฅผ ์ž…๋ ฅํ•˜๊ณ  โ€œSendโ€๋ฅผ ํด๋ฆญํ•ฉ๋‹ˆ๋‹ค. โ€œSent!โ€ ๊ฒฝ๊ณ ์ฐฝ(alert)์ด ๋‚˜ํƒ€๋‚˜๊ธฐ ์ „์— 3์ดˆ ์ •๋„ ์ง€์—ฐ๋œ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์ง€์—ฐ๋œ ์‹œ๊ฐ„ ๋™์•ˆ โ€œUndoโ€ ๋ฒ„ํŠผ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ˆ„๋ฅด์„ธ์š”. ์ด โ€œUndoโ€ ๋ฒ„ํŠผ์€ โ€œSent!โ€ ๋ฉ”์‹œ์ง€๊ฐ€ ๋‚˜ํƒ€๋‚˜์ง€ ์•Š๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. handleSend ์ค‘ ์ €์žฅ๋œ Timeout ID์— ๋Œ€ํ•ด clearTimeout์„ ํ˜ธ์ถœํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ โ€œUndoโ€๋ฅผ ํด๋ฆญํ•œ ํ›„์—๋„ โ€œSent!โ€ ๋ฉ”์‹œ์ง€๊ฐ€ ๊ณ„์† ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค. ์™œ ์ž‘๋™ํ•˜์ง€ ์•Š๋Š”์ง€ ์ฐพ์•„์„œ ๊ณ ์ณ๋ด…์‹œ๋‹ค.

import { useState } from 'react';

export default function Chat() {
  const [text, setText] = useState('');
  const [isSending, setIsSending] = useState(false);
  let timeoutID = null;

  function handleSend() {
    setIsSending(true);
    timeoutID = setTimeout(() => {
      alert('Sent!');
      setIsSending(false);
    }, 3000);
  }

  function handleUndo() {
    setIsSending(false);
    clearTimeout(timeoutID);
  }

  return (
    <>
      <input
        disabled={isSending}
        value={text}
        onChange={e => setText(e.target.value)}
      />
      <button
        disabled={isSending}
        onClick={handleSend}>
        {isSending ? 'Sending...' : 'Send'}
      </button>
      {isSending &&
        <button onClick={handleUndo}>
          Undo
        </button>
      }
    </>
  );
}