[Hook] useRef

OlMinJe·2025년 9월 2일

React

목록 보기
15/19

리액트 공식 문서를 참고한 정리 내용 (25.08 기준)

렌더링에 필요하지 않은 값을 참조할 수 있는 Hook이다.

const ref = useRef(initialValue)

컴포넌트의 최상위 레벨에서 useRef를 호출하여 ref를 선언한다.

import { useRef } from 'react';

function MyComponent() {
  const intervalRef = useRef(0);
  const inputRef = useRef(null);
  // ...

매개변수

initialValue

ref는 초기값(시작값)을 갖지만, 렌더링 이후에는 React가 더 이상 관여하지 않으므로 개발자가 자유롭게 변경할 수 있는 값이 된다.


반환값

current

  • 단일 프로퍼티를 가진 객체를 반환한다.
  • 처음엔 currentinitialValue가 들어가며, 이후에는 개발자가 마음대로 current 값을 바꿀 수 있다.
  • JSX에서 ref로 달아주면, React가 알아서 DOM 노드를 current에 넣어주고 매 렌더링마다 새 객체가 아니라 똑같은 객체를 계속 돌려준다.

주의사항

state랑 다름

  • ref.current는 그냥 자바스크립트 변수처럼 직접 바꿀 수 있다.
  • 하지만 state와 달리 바꿔도 컴포넌트가 다시 렌더링되지는 않는다.

렌더링에 쓰는 값은 넣지 말기

  • 화면에 그려야 하는 데이터(state처럼 쓰이는 값)를 ref.current 안에 두면 안 된다.
  • ref는 React가 추적하지 않기 때문에 UI가 안 맞아질 수 있다.

렌더링 중에는 건드리지 말기

  • 초기화할 때 빼고는 컴포넌트 함수가 실행되는 중에 ref.current를 읽거나 쓰면 동작이 꼬일 수 있다.

개발 모드에서 두 번 실행되는 이유

  • React는 버그를 잡으려고 개발 모드에서 컴포넌트를 일부러 두 번 실행한다.
  • 그래서 ref 객체도 두 번 만들어졌다가 하나는 버려진다.
  • 하지만 함수가 순수하게 작성되어 있다면 (즉, 같은 입력 → 같은 출력), 실제 동작에는 아무 문제 없다.

사용법

Ref로 값 참조하기

useRef값을 저장하는 상자 같은 것으로, ref.current에 원하는 값을 넣으면, 컴포넌트가 다시 렌더링되어도 값이 그대로 유지된다.
단, 이 값은 UI를 다시 그리게 만들지 않는다는 점이 state와 큰 차이이다.

const intervalRef = useRef(0);

intervalRef.current 안에 setInterval로 만든 ID를 저장한다.

intervalRef.current = intervalId;

이렇게 저장해두면 나중에 clearInterval(intervalRef.current)로 꺼내 쓸 수 있다.

ref를 사용하면 다음을 보장

  • (렌더링할 떄마다 재설정되는 일반 변수와 달리) 리렌더링 사이에 정보를 저장할 수 있다.
  • (리렌더링을 촉발하는 state 변수와 달리) 변경해도 리렌더링을 촉발하지 않는다.
  • (정보가 공유되는 외부 변수와 달리) 각각의 컴포넌트에 로컬로 저장된다.
import { useState, useRef } from "react";

export default function InputCounter() {
  const [text, setText] = useState("");       // 화면에 보여줄 값
  const renderCount = useRef(0);              // 렌더링 횟수 저장 (UI와는 무관)

  // 렌더링될 때마다 카운트 증가
  renderCount.current += 1;

  return (
    <div>
      <input
        value={text}
        onChange={(e) => setText(e.target.value)}  // state로 UI 업데이트
      />
      <p>입력한 값: {text}</p>
      <p>렌더링 횟수: {renderCount.current}</p>
    </div>
  );
}

useState (text) 사용자가 입력하면 값이 바뀌고 UI가 다시 렌더링돼서 화면이 업데이트됨.
useRef (renderCount) 렌더링할 때마다 숫자가 올라가지만, ref는 바뀌어도 화면을 다시 그리지 않음. 즉, “렌더링 횟수 기록용 저장소”로만 쓰임


ref.current는 렌더링 중에 읽거나 쓰면 안된다.

대신 이벤트 핸들러나 useEffect 안에서 사용해야 한다.
아 왜요~ 그냥 편하게 사용하려고 만든거 아닌가요?

React는 컴포넌트를 수학 함수처럼 다루길 원하기 때문에, 입력값(props, state, context)이 같으면 항상 같은 JSX 결과가 나와야 한다는 것이다.

하지만 렌더링 중에 ref.current를 바꾸거나 읽어버리면, (1)같은 입력인데도 결과가 달라질 수 있다. 그러면 (2)React의 예측 가능성이 깨지고, 버그가 생길 수 있다.

// ❌ 잘못된 예시 (렌더링 중에 ref 수정/읽기)
function MyComponent() {
  myRef.current = 123;              // 렌더링 중에 쓰기
  return <h1>{myRef.current}</h1>;  // 렌더링 중에 읽기
}

// ✅ 올바른 예시 (useEffect나 이벤트 핸들러에서 사용)
function MyComponent() {
  useEffect(() => {
    myRef.current = 123;  // Effect 안에서는 가능
  }, []);

  function handleClick() {
    console.log(myRef.current); // 이벤트 핸들러에서도 가능
  }

  return <button onClick={handleClick}>Click</button>;
}

Ref로 DOM 조작하기

useRef(null)을 사용해 ref 객체를 만들고 이 ref 객체를 JSX 엘리먼트의 ref 속성에 연결한다. 그러면 React가 실제 DOM 노드를 ref.current에 넣어준다.

이제 ref.current를 통해 DOM 메서드(ex. focus())를 직접 호출할 수 있다.

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

  function handleClick() {
    inputRef.current.focus(); // 버튼 클릭 시 input에 포커스
  }

  return (
    <>
      <input ref={inputRef} />
      <button onClick={handleClick}>포커스 주기</button>
    </>
  );
}

Ref로 DOM 조작하기 참고!


자식 컴포넌트의 DOM을 부모에서 조작하기

부모에서 버튼을 눌러 자식의 <input>에 포커스를 주고 싶을 때처럼 자식 컴포넌트 내부의 DOM을 부모 컴포넌트에서 직접 조작해야 할 때가 있다.
그럴 땐 ref를 부모에서 만들고, 자식에게 ref를 prop으로 넘겨서 DOM을 노출하면 된다.

import { useRef } from 'react';

function MyInput({ ref }) {
  return <input ref={ref} />;   // 부모가 넘겨준 ref를 input에 연결
}

export default function Form() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();   // 부모에서 자식의 input DOM을 조작
  }

  return (
    <>
      <MyInput ref={inputRef} />
      <button onClick={handleClick}>
        Focus the input
      </button>
    </>
  );
}
profile
큐트걸

0개의 댓글