Referencing Values with Refs

date
2026-01-22
order
1
link
  • Referencing Values with Refs โ€“ React

You will learn

  • ์ปดํฌ๋„ŒํŠธ์— ref๋ฅผ ์ถ”๊ฐ€ ํ•˜๋Š” ๋ฐฉ๋ฒ•
  • ref๊ฐ’ ์—…๋ฐ์ดํŠธ ํ•˜๋Š” ๋ฐฉ๋ฒ•
  • ref์™€ state์˜ ์ฐจ์ด
  • ref ์•ˆ์ „ํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•

Ref๋ž€

๊ณต์‹๋ฌธ์„œ์—์„œ ์ƒํƒœ๋ฅผ ์ปดํฌ๋„ŒํŠธ์˜ ๋ฉ”๋ชจ๋ฆฌ๋ผ๊ณ  ํ•œ๋‹ค. ์ƒํƒœ๊ฐ€ ๋ณ€ํ• ๋•Œ๋งˆ๋‹ค ๊ทธ์— ๋งž๋Š” ์ตœ์‹  ์ƒํƒœ์˜ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋‹ค์‹œ ๊ทธ๋ ค์ง€๊ธฐ ๋•Œ๋ฌธ์ธ๊ฑด๋ฐ, ์–ด๋–ค ๊ฐ’๋“ค์€ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๊ธฐ์–ตํ–ˆ์œผ๋ฉด ์ข‹๊ฒ ์ง€๋งŒ ๋ณ€ํ• ๋•Œ๋งˆ๋‹ค ์ƒˆ๋กœ ๋ Œ๋”๋ง ํ•˜์ง€๋Š” ์•Š์•˜์œผ๋ฉด ์ข‹๊ฒ ์œผ๋ฉด ํ•˜๋Š”๊ฒƒ๋“ค์ด ์žˆ๋‹ค.

๋Œ€ํ‘œ์ ์ธ ์˜ˆ์‹œ๋กœ ํƒ€์ด๋จธ๊ฐ€ ์žˆ๋‹ค.

mport { 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>
    </>
  );
}

์Šคํƒ€ํŠธ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด setInterval์ด ์‹คํ–‰๋˜๋ฉด์„œ 10ms ๋งˆ๋‹ค now ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธ ํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ทธ๋•Œ๋งˆ๋‹ค secondsPassed ๊ฐ’์ด ๊ณ„์‚ฐ๋˜๊ณ  ๊ทธ๊ฑธ ํ™”๋ฉด์— ํ‘œ์‹œํ•œ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ์ด๊ฑธ ๋ฉˆ์ถ”๋ ค๋ฉด setInterval์ด ๋ฐ˜ํ™˜ํ•œ id๋ฅผ ์‚ฌ์šฉํ•ด ์„ ๋ฉˆ์ถฐ์ฃผ์–ด์•ผ ํ•˜๋Š”๋ฐ(clearInterval(id)) ์ด ๊ฐ’์€ ๋ Œ๋”๋ง๊ณผ ๊ด€๋ จ์ด ์—†๊ธฐ ๋•Œ๋ฌธ์— ref๋กœ ์‚ฌ์šฉํ•œ๋‹ค.

state vs ref

ํƒ€์ด๋จธ id๋ฅผ ์ƒํƒœ๋กœ ํ•˜๋ฉด ์•ˆ๋˜๋‚˜?

์‚ฌ์‹ค ํƒ€์ด๋จธ Id๋ฅผ ์ƒํƒœ๋กœ ๊ด€๋ฆฌํ•ด๋„ ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ ํ•œ๋‹ค.

import { useState, useRef } from 'react';

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

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

    clearInterval(intervalId);
    setIntervalId(setInterval(() => {
      setNow(Date.now());
    }, 10));
  }

  function handleStop() {
    clearInterval(intervalId);
  }

  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>
    </>
  );
}

๊ทธ๋Ÿฐ๋ฐ ์™œ ์ƒํƒœ๋กœ ํ•˜๋ฉด ์•ˆ๋˜๊ณ  ref๋กœ ์‚ฌ์šฉํ•ด์•ผํ• ๊นŒ? ๋ฌธ์„œ์—์„œ ์ด์— ๋Œ€ํ•œ ๋‹ต์„ ์ฐพ์ง€๋Š” ๋ชปํ–ˆ๋‹ค.

ref์˜ ๋‚ด๋ถ€ ์›๋ฆฌ

๋ฆฌ์•กํŠธ ๋‚ด๋ถ€ ์›๋ฆฌ๋Š” setter ํ•จ์ˆ˜ ์—†์ด ์‚ฌ์šฉํ•˜๋Š” ์ƒํƒœ์™€ ํก์‚ฌํ•˜๋‹ค๊ณ  ํ•œ๋‹ค.

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

์˜ˆ์‹œ ๋ฌธ์ œ

1.

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 (
	  //...

handleUndo๋ฅผ ๋ˆŒ๋Ÿฌ๋„ ์ทจ์†Œ๋˜์ง€ ์•Š๋Š” ์ด์œ ๋Š” timeoutID๋ฅผ ์ผ๋ฐ˜ ๋ณ€์ˆ˜๋กœ ์„ ์–ธํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๋ณด๋‚ด๊ธฐ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด์„œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง ๋˜๋Š”๋Œ€ ์ผ๋ฐ˜ ๋ณ€์ˆ˜๋Š” ์‚ฌ๋ผ์ง„๋‹ค. ๊ทธ๋ž˜์„œ undo ๋ฒ„ํŠผ์€ ์•„๋งˆ clearTimeout(null) ์„ ํ˜ธ์ถœํ•˜๊ฒŒ ๋ ๊ฑฐ๊ณ  ๊ทธ๋ž˜์„œ ์ทจ์†Œ๋˜์ง€ ์•Š๋Š”๊ฒƒ.

2.

export default function Toggle() {
  const isOnRef = useRef(false);

  return (
    <button onClick={() => {
      isOnRef.current = !isOnRef.current;
    }}>
      {isOnRef.current ? 'On' : 'Off'}
    </button>
  );
}

ํ† ๊ธ€ ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ๋„ On๊ณผ Off ๋ฌธ๊ตฌ๊ฐ€ ๋ฐ”๋€Œ์ง€ ์•Š๋Š” ์ด์œ ๋Š”, ref๋Š” ๋ฆฌ๋ Œ๋”๋ง์„ ์ด‰๋ฐœํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. console.log(isOnRef.current)๋ฅผ ์ฐ์–ด๋ณด๋ฉด ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅผ๋•Œ๋งˆ๋‹ค ๊ฐ’์€ ๋ณ€ํ•˜๋Š”๊ฑธ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

3. debouncing

let timeoutID;

function DebouncedButton({ onClick, children }) {
  return (
    <button onClick={() => {
      clearTimeout(timeoutID);
      timeoutID = setTimeout(() => {
        onClick();
      }, 1000);
    }}>
      {children}
    </button>
  );
}

export default function Dashboard() {
  return (
    <>
      <DebouncedButton
        onClick={() => alert('Spaceship launched!')}
      >
        Launch the spaceship
      </DebouncedButton>
      <DebouncedButton
        onClick={() => alert('Soup boiled!')}
      >
        Boil the soup
      </DebouncedButton>
      <DebouncedButton
        onClick={() => alert('Lullaby sung!')}
      >
        Sing a lullaby
      </DebouncedButton>
    </>
  )
}

๋””๋ฐ”์šด์‹ฑ์ด๋ž€ ์งง์€ ์ˆœ๊ฐ„์— ์ด๋ฒคํŠธ๊ฐ€ ๋งŽ์ด ๋ฐœ์ƒํ• ๋•Œ ๋งˆ์ง€๋ง‰ ํ•œ๋ฒˆ๋งŒ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์„ ๋งํ•œ๋‹ค. ์œ„์—์„œ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅผ๋•Œ ๋งˆ๋‹ค ๊ธฐ์กด ํƒ€์ด๋จธ๋ฅผ ์ง€์šฐ๊ณ  ์ƒˆ๋กœ์šด ํƒ€์ด๋จธ๋ฅผ ์„ค์ •ํ•ด 1์ดˆ ๋’ค์— ์•Œ๋ฆผ์ฐฝ์ด ๋“ฑ์žฅํ•œ๋‹ค. ์œ„ ์ฝ”๋“œ์˜ ๋ฌธ์ œ๋Š” ๊ฐ ๋ฒ„ํŠผ๋“ค๋งˆ๋‹ค ๋…๋ฆฝ์ ์œผ๋กœ ๋””๋ฐ”์šด์‹ฑ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜์ง€ ๋ชปํ•œ๋‹ค๋Š” ๊ฒƒ์ธ๋ฐ, ๊ฐ„๋‹จํžˆ let timeoutID ๋ผ๊ณ  ์ปดํฌ๋„ŒํŠธ ๋ฐ–์—์„œ ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•˜๊ณ  ๊ทธ๊ฑธ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ชจ๋“  ๋ฒ„ํŠผ๋“ค์ด ํ•˜๋‚˜์˜ ๋ณ€์ˆ˜๋ฅผ ๊ณต์œ ํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๋ฒ„ํŠผ ์ปดํฌ๋„ŒํŠธ ์•ˆ์—์„œ ref๋ฅผ ์‚ฌ์šฉํ•ด ํ•ด๊ฒฐํ•œ๋‹ค.

4. ์ตœ์‹  state ์ฝ๊ธฐ

export default function Chat() {
  const [text, setText] = useState('');
  const textRef = useRef(text);

  function handleChange(e) {
    setText(e.target.value);
    textRef.current = e.target.value;
  }

  function handleSend() {
    setTimeout(() => {
      alert('Sending: ' + textRef.current);
    }, 3000);
  }

  return (
    <>
      <input
        value={text}
        onChange={handleChange}
      />
      <button
        onClick={handleSend}>
        Send
      </button>
    </>
  );
}

์‹ค์ œ๋กœ ์ด๋ ‡๊ฒŒ ์„ค๊ณ„ํ•ด์•ผ ํ•  ๊ฒฝ์šฐ๋Š” ๋งŽ์ง€ ์•Š์ง€๋งŒ (Usually, this behavior is what you want in an app. However, there may be occasional cases where you want some asynchronous code to read the latest version of some state.), ์œ„์˜ ์ฝ”๋“œ์ฒ˜๋Ÿผ ์ตœ์‹  ์ƒํƒœ๋ฅผ ์ฝ๊ธฐ ์œ„ํ•ด ref๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

alert() ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉด์„œ ์ „๋‹ฌ๋ฐ›์€ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋˜์–ด ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง ๋˜์–ด๋„ ๊ฐ™์€ ๊ฐ’์„ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. (๋ฆฌ์•กํŠธ๋Š” ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๊ณ  setter๋กœ ์•„์˜ˆ ์ƒˆ๋กœ์šด ๊ฐ’์œผ๋กœ ๋ณ€๊ฒฝํ•œ๋‹ค.)