React Refs & useRef — DOM에 대한 ‘비밀 백도어’ 🚪

발행: (2026년 2월 16일 오전 03:09 GMT+9)
6 분 소요
원문: Dev.to

Source: Dev.to

React에서 DOM 요소에 직접 접근해야 했지만, React가 방해하는 것처럼 느껴진 적 있나요?

바로 그때 useRef가 필요한 것입니다. 이를 실제 DOM에 손을 뻗을 수 있게 해주는 비밀 뒷문이라고 생각하세요 — React의 규칙을 어기지 않으면서.

State vs. Ref — 두 문장 버전

State → 변경이 발생하면 다시 렌더링됩니다. setter 함수를 사용해 업데이트합니다.

Ref → 변경이 조용히 일어납니다. 직접 변형하고, React는 전혀 반응하지 않습니다.

Refs는 자신을 위해 붙이는 메모와 같습니다. React는 그 메모에 무엇을 적었는지는 신경 쓰지 않습니다.

Ref 만들기

import { useRef } from "react";

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

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

무슨 일이 일어나는가

  1. useRef(null)은 객체를 생성한다: { current: null }.
  2. 그 객체가 <input>ref prop에 전달된다.
  3. React가 inputRef.current에 해당 입력 요소의 실제 DOM 노드를 채워 넣는다.

이제 inputRef.current는 페이지에 있는 실제 <input> 요소이다.

실제 예시: 새 콘텐츠 자동 스크롤

import { useRef, useEffect, useState } from "react";

function RecipeApp() {
  const [recipe, setRecipe] = useState(null);
  const recipeSectionRef = useRef(null);

  async function fetchRecipe() {
    const response = await getRecipeFromAI(); // pretend API call
    setRecipe(response);
  }

  useEffect(() => {
    if (recipe && recipeSectionRef.current) {
      recipeSectionRef.current.scrollIntoView({ behavior: "smooth" });
    }
  }, [recipe]);

  return (
    <div>
      <button onClick={fetchRecipe}>Get a Recipe</button>

      {recipe && (
        <section ref={recipeSectionRef}>
          <h2>{recipe.title}</h2>
          <p>{recipe.instructions}</p>
        </section>
      )}
    </div>
  );
}

단계별로 일어나는 일

  1. 사용자가 Get a Recipe 버튼을 클릭합니다.
  2. API가 데이터를 반환 → 상태가 업데이트 → React가 다시 렌더링됩니다.
  3. ref가 연결된 <section>이 이제 DOM에 존재합니다.
  4. useEffect가 실행되어 레시피가 로드된 것을 확인하고 scrollIntoView()를 호출합니다.
  5. 브라우저가 부드럽게 레시피 섹션까지 스크롤됩니다.

document.getElementById도 없고, 쿼리 셀렉터도 없습니다. 깔끔한 ref만 사용합니다.

“하지만 ID만 사용하면 안 될까?”

You could do this:

...
document.getElementById("recipe-section").scrollIntoView();

It works… until it doesn’t. React encourages reusable components. Rendering the same component twice would create duplicate IDs, which is invalid HTML and a source of bugs.

Refs avoid this entirely because they’re scoped to each component instance. Two instances → two separate refs → zero conflicts.

정신 모델 요약 시트

StateRef
다시 렌더링을 트리거합니까?아니오
업데이트 방법setter 함수직접 변이 (myRef.current = …)
일반적인 사용UI 데이터DOM 접근, 타이머, 이전 값
형태설정한 모든 것{ current: value }

기억해야 할 세 가지 간단한 규칙

  1. Refs는 단순히 상자일 뿐입니다.
    useRef(initialValue){ current: initialValue }를 반환합니다 – current라는 하나의 선반을 가진 상자입니다.

  2. 자유롭게 변형하세요.
    state와 달리, myRef.current = "whatever"와 같이 할당해도 재렌더링을 일으키지 않습니다.

  3. ref prop은 마법과 같습니다 — 하지만 네이티브 요소에만 적용됩니다.
    <input ref={myRef}>는 React가 myRef.current에 해당 DOM 노드를 채워 넣게 합니다.
    <MyComponent ref={myRef}>forwardRef를 사용하지 않는 한 단순히 ref라는 일반 prop을 전달할 뿐입니다.

TL;DR

  • useRef는 지속적인 변경 가능한 컨테이너를 생성합니다: { current: value }.
  • .current를 변경해도 리렌더링이 발생하지 않습니다.
  • ref prop을 통해 DOM 요소에 연결하면 해당 노드에 직접 접근할 수 있습니다.
  • 스크롤링, 입력 포커스 지정, 요소 측정, 혹은 렌더링 사이에 값을 저장하면서 업데이트를 트리거하지 않아야 할 때 이상적입니다.

Refs는 처음에 어색하게 느껴질 수 있지만, 한 번 “이해”하면 두 번째 천성처럼 자연스럽습니다. DOM에 직접 핸들을 잡거나 렌더링 사이에 안정적인 변경 가능한 값을 필요로 할 때 사용하세요.

0 조회
Back to Blog

관련 글

더 보기 »

미친 React key

tsx에서 map을 통한 렌더링 export function Parent { const array, setArray = useState(1, 2, 3, 4, 5); useEffect(() => { setTimeout(() => { setArray(prev => [6, 7, 8, 9, 10, ...prev]); ... }); }); }

Server Components는 SSR이 아니다!

SSR vs. React Server Components 개발 세계에서 React Server Components(RSC)는 종종 또 다른 형태의 Server‑Side Rendering(SSR)으로 오해받는다. 두 가지 모두…