티스토리 뷰

 

React로 개발을 하다 보면 선언적인 방식(Declarative)만으로는 해결하기 어려운 상황이 발생합니다. 특정 입력창에 포커스를 주거나, 스크롤 위치를 계산하거나, 혹은 리렌더링과 상관없이 값을 유지해야 할 때가 바로 그때입니다.

오늘은 React의 핵심 훅 중 하나인 useRef의 두 가지 주요 용도와 실전 예제를 살펴보겠습니다.

1. useRef란 무엇인가?

useRef는 이름 그대로 **참조(Reference)**를 만들기 위한 훅입니다. 이 훅은 .current라는 단일 속성을 가진 변경 가능한 객체를 반환합니다.

주요 특징

  1. 리렌더링을 유발하지 않음: useRef의 값이 변해도 컴포넌트는 다시 그려지지 않습니다.
  2. 저장 공간: 컴포넌트의 전 생애주기 동안 값이 유지됩니다.
  3. DOM 접근: 실제 HTML 요소에 직접 접근할 수 있는 통로 역할을 합니다.

2. DOM 요소 직접 조작하기 (포커스 제어)

가장 대표적인 사용 사례는 특정 DOM 노드에 포커스를 주거나 애니메이션을 직접 제어하는 것입니다.

예제: 로그인 폼 자동 포커스

페이지가 로드되자마자 아이디 입력창에 포커스가 가도록 하거나, 버튼 클릭 시 특정 입력창으로 이동하는 기능을 구현해 보겠습니다.

import React, { useRef, useEffect } from 'react';

function LoginForm() {
  const idInputRef = useRef(null);
  const pwInputRef = useRef(null);

  // 1. 컴포넌트가 마운트될 때 ID 입력창에 포커스
  useEffect(() => {
    idInputRef.current.focus();
  }, []);

  const handleNext = () => {
    // 2. ID 입력 후 특정 동작 시 PW 입력창으로 포커스 이동
    pwInputRef.current.focus();
  };

  return (
    <div style={{ padding: '20px', display: 'flex', flexDirection: 'column', gap: '10px' }}>
      <h2>로그인</h2>
      <input 
        ref={idInputRef} 
        type="text" 
        placeholder="아이디를 입력하세요" 
      />
      <input 
        ref={pwInputRef} 
        type="password" 
        placeholder="비밀번호를 입력하세요" 
      />
      <button onClick={handleNext}>비밀번호 입력하러 가기</button>
    </div>
  );
}

export default LoginForm;

3. 변수 저장소로 활용하기 (Mutable Ref)

useState는 값이 바뀔 때마다 컴포넌트를 다시 렌더링합니다. 하지만 렌더링과 상관없이 내부적으로 값을 기억해야 할 때 useRef는 훌륭한 저장소가 됩니다.

예제: 타이머 ID 관리

setInterval이나 setTimeout을 사용할 때 반환되는 ID 값을 저장하여 나중에 중지(Clear)할 때 사용합니다.

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

function StopWatch() {
  const [startTime, setStartTime] = useState(null);
  const [now, setNow] = useState(null);
  
  // 리렌더링을 유발하지 않고 타이머 ID를 저장
  const timerRef = useRef(null);

  const handleStart = () => {
    setStartTime(Date.now());
    setNow(Date.now());

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

  const handleStop = () => {
    clearInterval(timerRef.current);
  };

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

  return (
    <div style={{ textAlign: 'center' }}>
      <h1>경과 시간: {secondsPassed.toFixed(3)}</h1>
      <button onClick={handleStart}>시작</button>
      <button onClick={handleStop}>중지</button>
    </div>
  );
}

4. 실전 활용: 스크롤 애니메이션 제어

특정 버튼을 눌렀을 때 특정 섹션으로 부드럽게 스크롤하는 기능은 useRef가 없으면 구현하기 까다롭습니다.

import React, { useRef } from 'react';

function ScrollDemo() {
  const sectionRef = useRef(null);

  const scrollToSection = () => {
    sectionRef.current.scrollIntoView({
      behavior: 'smooth',
      block: 'start',
    });
  };

  return (
    <div>
      <nav style={{ position: 'fixed', top: 0, background: '#fff', width: '100%' }}>
        <button onClick={scrollToSection}>아래 섹션으로 이동</button>
      </nav>
      
      <div style={{ height: '150vh', paddingTop: '50px' }}>
        <p>스크롤을 내려보거나 위 버튼을 클릭하세요.</p>
      </div>

      <div 
        ref={sectionRef} 
        style={{ height: '500px', background: '#f0f0f0', display: 'flex', alignItems: 'center', justifyContent: 'center' }}
      >
        <h2>짠! 여기에 도착했습니다.</h2>
      </div>
    </div>
  );
}

5. useState vs useRef 비교 요약

특징useStateuseRef

반환값 [상태, 세터함수] { current: 값 }
리렌더링 여부 값이 변하면 리렌더링 됨 값이 변해도 리렌더링 안 됨
주 용도 UI에 반영해야 하는 데이터 DOM 접근, UI와 무관한 변수 관리
값의 동기화 비동기적으로 업데이트됨 즉시 업데이트됨

6. 주의사항

  1. 렌더링 도중 ref.current를 쓰거나 읽지 마세요.
    • React는 렌더링 도중 DOM 노드가 생성되거나 변경될 수 있음을 가정합니다. ref는 항상 useEffect 안이나 이벤트 핸들러 안에서 사용하는 것이 안전합니다.
  2. 과도한 사용 금지.
    • 대부분의 UI 로직은 Props와 State로 해결하는 것이 좋습니다. useRef는 명령적인(Imperative) 방식이므로 꼭 필요한 경우에만 사용하세요.

useRef를 적재적소에 활용하면 React의 선언적 한계를 극복하고 훨씬 유연한 컴포넌트를 설계할 수 있습니다.

반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/04   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30
글 보관함