본문 바로가기
React

[React] useRef로 리렌더링 없는 변수 저장하기 (setInterval 예제)

by teamnova 2025. 10. 26.
728x90

안녕하세요. 

오늘은 useRef를 컴포넌트의 리렌더링(re-rendering)을 유발하지 않는 값 저장소로 활용하는 예제를 만들어 보겠습니다. 

 

useRef

useState와 useRef는 둘 다 무언가를 '기억'하지만, 그 성격이 완전히 다릅니다.

 이 차이를 setInterval 타이머 예제로 확실하게 알아보겠습니다.

 

  • useState
    • 값이 바뀌면 ( setCount(1) ) 즉시 리렌더링이 예약됩니다.
    • UI에 보여야 하는 값을 저장합니다.
  • useRef
    • myRef.current = '...' 처럼 값을 바꿔도, React는 전혀 신경 쓰지 않습니다. (리렌더링 X)
    •  UI와는 상관없는 내부 관리용 값을 저장할 때 씁니다.

 

 

예제 - setInterval 타이머 만들기


화면에 보이는 초(second): 0, 1, 2... 는 useState여야 합니다. 
setInterval을 실행했을 때 반환되는 타이머 ID: 123 같은 숫자는 useRef여야 합니다. 

왜 타이머 ID를 useState에 저장하면 안 될까요?

setTimerId(123)을 호출하는 순간, 불필요한 리렌더링이 발생하기 때문입니다.

import React, { useState, useRef, useEffect } from 'react';
import './App.css';

function App() {
  //    count가 바뀌면 화면이 리렌더링됩니다.
  const [count, setCount] = useState(0);


  //    intervalRef.current 값은 바뀌어도 리렌더링되지 않습니다.
  const intervalRef = useRef(null);

  // 컴포넌트가 사라질 때(unmount) 타이머를 정리(cleanup)합니다.
  // (메모리 누수 방지를 위한 중요한 습관입니다)
  useEffect(() => {
    // 이 return 함수는 컴포넌트가 사라질 때 호출됩니다.
    return () => {
      if (intervalRef.current) {
        clearInterval(intervalRef.current);
      }
    };
  }, []); // 의존성 배열 []: 컴포넌트가 처음 마운트될 때 1번만 실행

  // '시작' 버튼 핸들러
  const handleStart = () => {
    // 이미 타이머가 실행 중이라면 (intervalRef.current에 값이 있다면)
    // 중복 실행을 방지합니다.
    if (intervalRef.current !== null) {
      return;
    }

    // 1초(1000ms)마다 count state를 1씩 올립니다.
    // intervalRef.current에 setInterval이 반환한 ID를 "조용히" 저장합니다.
    intervalRef.current = setInterval(() => {
      // setCount는 리렌더링을 유발합니다 (전광판 변경!)
      setCount(prevCount => prevCount + 1);
    }, 1000);

    console.log('타이머 시작, ID:', intervalRef.current);
  };

  // '정지' 버튼 핸들러
  const handleStop = () => {
    if (intervalRef.current === null) {
      return;
    }

    // intervalRef.current에 "조용히" 저장해 둔 ID를 사용해 타이머를 정지합니다.
    clearInterval(intervalRef.current);
    intervalRef.current = null; // ID를 null로 리셋 (정지 상태 표시)

    console.log('타이머 정지!');
  };

  // '초기화' 버튼 핸들러
  const handleReset = () => {
    handleStop(); // 1. 일단 타이머를 멈추고
    setCount(0);  // 2. 화면에 보이는 숫자를 0으로 리셋 (리렌더링!)
  };

  return (
    <div className="App">
      <header className="App-header">
        <h1>useRef로 타이머 만들기</h1>
        <p style={{ fontSize: '3rem' }}>{count}초</p>
        <div style={{ display: 'flex', gap: '10px' }}>
          <button onClick={handleStart}>시작</button>
          <button onClick={handleStop}>정지</button>
          <button onClick={handleReset}>초기화</button>
        </div>
      </header>
    </div>
  );
}

export default App;

 

결과