React에서 ref, useRef는 왜 필요한가?

 

React에서 ref, useRef는 왜 필요한가? — DOM 직접 제어가 필요한 순간

React 이전: DOM을 직접 조작하던 시대

React가 나오기 전에는 화면을 바꾸려면 DOM을 직접 건드려야 했습니다.

// jQuery 시절 (2010년대 초)
document.getElementById('name').innerText = '홍길동';
document.getElementById('age').innerText = '25';
document.getElementById('list').innerHTML = '<li>항목1</li><li>항목2</li>';

작은 앱에서는 괜찮았지만, 앱이 커지면 문제가 터집니다.

  • DOM 수정 코드가 수백 줄로 폭발
  • “지금 화면에 뭐가 보이고 있지?” 추적 불가능
  • 버그 발생 → 어디서 DOM을 잘못 건드렸는지 찾기 지옥

React의 해결책 (2013): “DOM은 내가 관리할게”

React는 이 혼란을 정리하기 위해 등장했습니다. 핵심 아이디어는 간단합니다.

개발자는 데이터(state)만 바꾸면 된다. DOM 업데이트는 React가 알아서 한다.

// 개발자는 데이터(state)만 바꾸면 됨
const [name, setName] = useState('홍길동');

// React가 알아서 DOM을 업데이트
return <div>{name}</div>;

setName('김철수');
// React: "name이 바뀌었으니 div를 업데이트해야지"

흐름: 개발자 → state 변경 → React → DOM 업데이트

개발자가 DOM을 직접 건드릴 필요가 없어졌습니다.


그런데 예외가 생김: “React야, 네가 못 하는 게 있어”

React가 아무리 좋아도, 처리 못 하는 영역이 있습니다.

케이스 왜 React가 못 하나
input.focus() 포커스는 DOM API로만 가능
element.scrollIntoView() 스크롤도 DOM API로만 가능
contentEditable 브라우저가 직접 DOM을 수정, React 관여 불가
외부 라이브러리 연동 지도, 비디오 플레이어 등 DOM 직접 제어 필요

이런 경우를 위해 “React를 우회해서 DOM에 직접 접근하는 탈출구” 가 필요했고, 그게 바로 ref입니다.


ref의 역할: “React의 탈출구”

// React 방식 — state로 제어 (보통은 이걸 씀)
const [value, setValue] = useState('');
<input value={value} onChange={e => setValue(e.target.value)} />

// ref 방식 — DOM 직접 접근 (React가 못 하는 것만)
const inputRef = useRef(null);
<input ref={inputRef} />

inputRef.current.focus();       // 포커스 → React로 불가능
inputRef.current.scrollHeight;  // 높이 측정 → React로 불가능

ref의 두 가지 사용 방식

1. useRef (객체 ref)

하나의 DOM 요소를 참조할 때 사용합니다.

const myRef = useRef<HTMLDivElement>(null);

<div ref={myRef}>내용</div>

// React가 DOM 생성 후 myRef.current = div요소 를 넣어줌
// 이후 myRef.current.focus() 같은 DOM 조작 가능

2. ref 콜백 (함수 ref)

여러 요소를 동적으로 참조할 때 유용합니다.

<div ref={(el) => {
  // el = 실제 DOM 요소
  // React가 이 div를 DOM에 넣는 순간 이 함수를 호출해줌
}} />

내부 동작 순서:

React 렌더링
  → 가상 DOM 생성
    → 실제 DOM에 <div> 생성
      → ref 콜백 호출: ref(div요소)    ← 이 시점!
        → 화면에 표시

DOM에서 제거될 때는 ref(null)로 한 번 더 호출됩니다.

map에서 ref 콜백이 특히 유용한 이유

// useRef → 1개의 값만 저장 가능. 블록 3개면?
const ref = useRef(null); // 마지막 블록만 참조됨 ❌

// ref 콜백 → 각 블록마다 개별 실행됨
blocks.map(block => (
  <div ref={(el) => {
    // block 1일 때 호출 ✓
    // block 2일 때 호출 ✓
    // block 3일 때 호출 ✓
    // 각각의 el이 다른 DOM 요소!
  }} />
))

onInput도 같은 맥락

ref와 마찬가지로, onInput도 React 바깥 영역을 다루기 위해 존재합니다.

onChange onInput
<input>, <textarea> 값 바뀔 때 발생 값 바뀔 때 발생
contentEditable div 동작 안 함 동작함

contentEditable은 일반 <input>이 아니라 브라우저가 직접 DOM을 수정하는 방식입니다.
React의 onChange는 폼 요소용이라 여기선 안 먹히고, 브라우저 네이티브 이벤트인 onInput으로만 잡을 수 있습니다.

// <input>에서는 이렇게 쓰지만
<input onChange={(e) => setValue(e.target.value)} />

// contentEditable에서는 이렇게 써야 함
<div contentEditable onInput={(e) => {
  const text = e.currentTarget.innerText;  // DOM에서 직접 읽기
}} />

정리

React 기본 방식 ref / onInput
언제 state → 화면 업데이트 DOM 직접 접근이 필요할 때
React가 DOM을 대신 관리해줌 React가 못 하는 영역이 있어서
예시 {name} 렌더링 focus(), 초기 텍스트 삽입, contentEditable

ref는 React가 “나는 여기까지만 할게, 나머지는 너가 해”라고 열어준 문입니다.
가능하면 안 쓰는 게 좋지만, contentEditable 같은 경우엔 필수입니다.

 

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다