useRef 자세히 알아보기

2025. 10. 24. 18:41·Frontend/React

React를 배우다 보면 이런 상황을 한 번쯤 마주하게 된다.

“DOM 요소나 값을 직접 참조할 수도 있는데, 왜 굳이 useRef를 써야 할까?”

 

이 질문은 단순히 문법의 문제가 아니라, React의 렌더링 메커니즘과 상태 관리 철학을 이해하느냐의 문제이다.

이번 글에서는 React 렌더링 구조와 상태 관리의 관점에서 이 이유를 자세히 알아본다.

 

1. React는 ‘UI = state의 함수’ 구조다.

 

React의 핵심 원칙은 다음과 같다.

“UI는 상태(state)의 결과물이다.”

 

즉, 컴포넌트는 상태가 변할 때마다 다시 렌더링되어야 최신 UI를 보여줄 수 있다.

그런데 만약 React의 상태 관리 시스템 밖에서 직접 DOM을 건드리거나, 일반 변수를 수정한다면 어떻게 될까? React는 그 변화를 인지하지 못하기 때문에 렌더링이 일어나지 않거나, 잘못된 UI 상태를 보여줄 수 있다. 이 문제를 해결하기 위해 useRef가 존재한다.

 

2. useRef는 “렌더링 사이에서 유지되는 상자”다.

useRef는 간단히 말해, 값을 저장하지만 그 값이 바뀌어도 렌더링을 발생시키지 않는 저장소이다.

const ref = useRef(initialValue);

위와 같은 경우, ref.current에 원하는 값을 저장할 수 있는데, 이 값은 컴포넌트가 리렌더링되어도 유지된다. 또한 ref.current를 변경해도 렌더링은 일어나지 않는다. 즉, useRef는 “렌더링 사이에 변하는 값을 기억하기 위한 안전한 공간”인 것이다.

 

3. 왜 ‘직접 참조’보다 useRef를 써야 할까?

React의 렌더링 구조와 관련된 세 가지 이유가 있다.

1. 렌더링 구조와 일관성 유지

React의 렌더링은 순수 함수처럼 동작해야 한다.

function Component() {
  let count = 0; // 렌더링 시마다 초기화됨 ❌
  const ref = useRef(0); // 렌더링 사이에서도 값 유지 ⭕

  const handleClick = () => {
    count += 1;         // 다시 렌더링되면 count는 초기화됨 ❌
    ref.current += 1;   // 리렌더링돼도 유지됨 ⭕

    console.log(count, ref.current);
  };
}

즉, 일반 변수는 매 렌더링마다 새로 만들어지지만, useRef는 렌더링 간에도 같은 객체를 참조하기 때문에 상태가 유지된다. “렌더링 간 값의 지속성(persistence)”이 필요할 때는 useRef가 정답인 것이다.

 

2. DOM 접근 시 안전성 확보

DOM을 직접 조작하려면 document.querySelector() 같은 방식을 떠올릴 수 있는데, 이는 *React의 Virtual DOM 체계와 충돌할 수 있다는 문제가 있다.

function App() {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current.focus(); // 안전하게 DOM에 접근 가능
  }, []);

  return <input ref={inputRef} />;
}

위처럼 ref를 사용하면 React가 렌더링한 DOM 노드에 안전하게 접근할 수 있다. 이는 React의 관리 하에 있는 요소를 직접 접근하되, React의 구조를 깨지 않는 유일한 방법이다.

 

*React의 Virtual DOM 체계와 충돌

1. Virtual DOM과 실제 DOM의 관계

React는 Virtual DOM(Virtual DOM 트리)을 사용해 UI를 관리한다.

  1. 컴포넌트가 렌더링되면 JSX → Virtual DOM 객체로 변환된다.
  2. 상태(state)나 props가 바뀌면 React는 새 Virtual DOM을 만들고 이전 Virtual DOM과 비교(diffing)한다.
  3. 차이가 있으면 최소한의 실제 DOM만 업데이트한다.

즉, React는 Virtual DOM을 기준으로 실제 DOM을 업데이트한다.

 

2. 직접 DOM을 조작하면 생기는 문제

만약 document.querySelector() 등으로 직접 DOM을 조작하면:

<div id="count">{count}</div>
document.getElementById('count').innerText = 999;

위와 같은 상황에서, React는 여전히 Virtual DOM을 기준으로 렌더링하는데, 다음 렌더링에서 Virtual DOM의 값이 실제 DOM을 덮어씌운다. 결과적으로, 직접 변경한 내용이 사라지거나 반영되지 않는 UI 불일치가 발생한다.

 

 

3. useRef를 사용하면 안전한 이유

useRef는 React의 렌더링과 분리된 안전한 참조 공간을 제공한다.

const inputRef = useRef(null);

useEffect(() => {
  inputRef.current.focus(); // 안전하게 DOM 조작
}, []);

 

React가 Virtual DOM과 실제 DOM을 관리하는 사이클을 방해하지 않고, 렌더링과 관계 없는 조작(포커스, 스크롤 등)만 수행한다.

 

3. 불필요한 리렌더링 방지

useState는 값이 바뀌면 무조건 렌더링이 일어나지만, useRef는 아니다. 즉, 렌더링과 무관한 값이라면, useRef로 관리하는 것이 효율적이다.

const [value, setValue] = useState(0);   // 값 변경 시 렌더링 발생
const ref = useRef(0);                   // 값 변경해도 렌더링 X

setValue(value + 1);     // 컴포넌트 리렌더
ref.current += 1;         // 렌더링 없이 내부 값만 변경

 

실무 예시:

 

  • 타이머(setInterval) ID 저장
  • 이전 props / state 비교용 값 저장
  • focus 상태나 스크롤 위치 저장

 

 

4. 언제 useRef를 써야 할까?

상황 useRef 사용 여부 이유
DOM 요소 제어 ✅ React 구조 내 안전한 접근
타이머, ID 등 렌더링과 무관한 값 ✅ 불필요한 렌더링 방지
이전 값 기억 (prevProps, prevState) ✅ 상태 비교를 위한 저장소
단순 UI 표시용 상태 ❌ useState로 관리하는 게 맞음
전역적 상태 관리 ❌ Context나 Redux로 관리

 

5. 마무리

React의 렌더링 구조에서 useRef는 단순한 “참조도구”가 아니라, 렌더링 사이에서 변하는 데이터를 안전하게 관리하기 위한 최소 단위의 상태 저장소이다. “렌더링과 무관한 값은 useRef로, 렌더링에 영향을 주는 값은 useState로.”

'Frontend > React' 카테고리의 다른 글

상태(stateful) & 비상태(stateless) 컴포넌트  (0) 2025.10.25
React에서 비동기 처리 최적화하기  (0) 2025.10.25
useEffect의 cleanup 함수 알아보기  (0) 2025.10.24
React 메모이제이션과 최적화  (0) 2025.10.24
Virtual DOM  (0) 2025.10.16
'Frontend/React' 카테고리의 다른 글
  • 상태(stateful) & 비상태(stateless) 컴포넌트
  • React에서 비동기 처리 최적화하기
  • useEffect의 cleanup 함수 알아보기
  • React 메모이제이션과 최적화
JTB
JTB
웹/앱 개발 정보를 공유하고 있습니다.
  • JTB
    JTechBlog
    JTB
  • 전체
    오늘
    어제
    • All About Programming;)
      • Computer Science
        • Terminology and Concepts
        • Network
        • Operating System
        • Database
        • Data Structure
        • Web Development
      • Frontend
        • Javascript Essentials
        • Perfomance Optimization
        • JS Patterns
        • React
        • Next.js
        • Flutter
        • Testing
      • Backend
        • Node.js
      • DevOps
        • Docker & Kubernetes
      • Coding Test
        • LeetCode
        • Programmers
      • Tech Books & Lectures
        • Javascript_Modern JS Deep d..
        • Network_IT 엔지니어를 위한 네트워크 입문
      • Projects
        • PolyLingo_2025
        • Build Your Body_2024
        • JStargram_2021
        • Covid19 Tracker_2021
        • JPortfolio_2021
      • BootCamp_Codestates
        • TIL
        • TILookCloser
        • Pre Tech Blog
        • IM Tech Blog
        • Daily Issues and DeBugging
        • First Project
        • Final Project
        • Sprint Review
        • Good to Know
        • Socrative Review
        • HTML &amp; CSS
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 글쓰기
    • 관리
  • 공지사항

  • 인기 글

  • 태그

    딥다이브
    Time complexity and Space complexity
    Javascript Essentials
    TCP/IP
    프론트엔드 성능 최적화 가이드
    need a database
    CPU scheduling algorithm
    VoiceJournal
    스코프
    indie hacker
    structure of os
    js pattern
    Binary Tree BFS
    Shared resources
    커리어
    Threads and Multithreading
    Data Structure
    How memory manage data
    Operating System
    이벤트
    polylingo
    자바스크립트 딥다이브
    Network
    모던 자바스크립트 Deep Dive
    mobile app
    database
    leetcode
    testing
    DOM
    자바스크립트
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
JTB
useRef 자세히 알아보기
상단으로

티스토리툴바