| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 | 31 |
- 자바스크립트
- input
- 배열
- 문자열
- 테스트자동화
- java테스트
- math
- string
- ArrayList
- 스프링부트
- junit
- 단위테스트
- vscode
- 정규식
- Array
- html
- 자바
- js
- 자바문법
- Java
- CSS
- list
- junit5
- HashMap
- 인텔리제이
- IntelliJ
- javascript
- Visual Studio Code
- SpringBoot
- Eclipse
- Today
- Total
어제 오늘 내일
[React] useRef 사용법: 렌더링 없이 값을 기억하고 DOM에 접근하기 본문
React를 배우다 보면 useState만으로는 해결하기 애매한 상황을 만나게 됩니다. "값은 기억하고 싶은데 화면은 다시 그리고 싶지 않을 때", 혹은 "특정 input에 직접 포커스를 주고 싶을 때" 같은 경우죠. 이럴 때 사용하는 것이 바로 useRef Hook입니다. 이 글에서는 useRef의 두 가지 핵심 용도를 예제와 함께 알아보겠습니다.
useRef란?
useRef는 변경 가능한(mutable) 값을 담는 상자를 만들어주는 Hook입니다. 이 상자는 .current라는 프로퍼티를 가지며, 여기에 어떤 값이든 담아둘 수 있습니다.
import { useRef } from 'react';
function Component() {
// 초기값 0을 담은 ref 객체를 생성
// ref는 { current: 0 } 형태의 객체
const countRef = useRef(0);
// 값에 접근할 때는 .current 사용
console.log(countRef.current); // 0
}useRef의 가장 중요한 특징은 두 가지입니다.
- 값이 바뀌어도 컴포넌트를 리렌더링하지 않는다
- 리렌더링이 일어나도 값이 유지된다
이 특징 때문에 useRef는 크게 두 가지 용도로 쓰입니다. 하나씩 살펴보겠습니다.
용도 1: 렌더링과 무관한 값 저장하기
useState와 useRef의 가장 큰 차이는 리렌더링 여부입니다.
import { useState, useRef } from 'react';
function Counter() {
// useState: 값이 바뀌면 화면이 다시 그려짐
const [stateCount, setStateCount] = useState(0);
// useRef: 값이 바뀌어도 화면은 다시 그려지지 않음
const refCount = useRef(0);
const handleClick = () => {
// ref 값은 직접 .current를 수정 (setState 같은 함수 없음)
refCount.current += 1;
console.log('ref 값:', refCount.current); // 콘솔에는 증가한 값이 찍힘
// 하지만 화면의 refCount는 갱신되지 않음 (리렌더링이 안 되니까)
};
return (
<div>
<p>state: {stateCount}</p>
<p>ref: {refCount.current}</p>
<button onClick={handleClick}>ref 증가</button>
</div>
);
}위 예제에서 버튼을 눌러 refCount를 증가시켜도 화면의 ref 값은 바뀌지 않습니다. 콘솔에는 증가한 값이 찍히지만, 리렌더링이 일어나지 않으니 화면은 그대로입니다. stateCount가 바뀌어 리렌더링이 일어나야 비로소 갱신된 ref 값이 화면에 반영됩니다.
이 특성 덕분에 useRef는 화면에 보여줄 필요는 없지만 계속 기억해야 하는 값을 저장하는 데 유용합니다.
function Timer() {
// setInterval이 반환하는 타이머 ID를 저장
// 이 값은 화면에 보여줄 필요가 없고, 리렌더링을 유발할 필요도 없음
const timerId = useRef(null);
const startTimer = () => {
// 타이머 ID를 ref에 보관
timerId.current = setInterval(() => {
console.log('1초마다 실행');
}, 1000);
};
const stopTimer = () => {
// 저장해둔 ID로 타이머를 정지
clearInterval(timerId.current);
};
return (
<div>
<button onClick={startTimer}>시작</button>
<button onClick={stopTimer}>정지</button>
</div>
);
}만약 타이머 ID를 useState로 관리했다면, ID가 저장될 때마다 불필요한 리렌더링이 발생했을 것입니다. useRef를 쓰면 이런 낭비 없이 값을 관리할 수 있습니다.
용도 2: DOM 요소에 직접 접근하기
useRef의 또 다른 대표적인 용도는 실제 DOM 요소에 접근하는 것입니다. React에서는 보통 DOM을 직접 다루지 않지만, 포커스 설정이나 스크롤 이동처럼 어쩔 수 없이 직접 접근해야 하는 경우가 있습니다.
import { useRef } from 'react';
function SearchInput() {
// DOM 요소를 담을 ref 생성 (초기값은 null)
const inputRef = useRef(null);
const focusInput = () => {
// .current에 실제 <input> DOM 요소가 담겨 있음
// 순수 자바스크립트처럼 focus() 메서드 호출 가능
inputRef.current.focus();
};
return (
<div>
{/* ref 속성에 inputRef를 연결하면 */}
{/* React가 이 input의 실제 DOM을 inputRef.current에 넣어줌 */}
<input ref={inputRef} type="text" placeholder="검색어 입력" />
<button onClick={focusInput}>검색창에 포커스</button>
</div>
);
}ref 속성에 ref 객체를 연결하면, React가 렌더링 후 해당 DOM 요소를 자동으로 .current에 넣어줍니다. 그 후에는 inputRef.current.focus()처럼 순수 자바스크립트 DOM API를 그대로 사용할 수 있습니다.
실전 예제: 마운트 시 자동 포커스
컴포넌트가 화면에 나타나자마자 input에 포커스를 주는 것도 자주 쓰이는 패턴입니다.
import { useRef, useEffect } from 'react';
function LoginForm() {
const inputRef = useRef(null);
// 컴포넌트가 처음 마운트될 때 한 번 실행
useEffect(() => {
// 화면에 나타나자마자 이메일 입력창에 자동 포커스
inputRef.current.focus();
}, []); // 빈 배열: 최초 1회만 실행
return (
<form>
<input ref={inputRef} type="email" placeholder="이메일" />
<input type="password" placeholder="비밀번호" />
</form>
);
}useRef vs useState: 언제 무엇을 쓸까?
두 Hook은 비슷해 보이지만 목적이 다릅니다. 아래 표로 정리했습니다.
| 구분 | useState | useRef |
| 값 변경 시 리렌더링 | O (일어남) | X (일어나지 않음) |
| 값 접근 방법 | 변수 직접 사용 | .current로 접근 |
| 값 변경 방법 | setState 함수 |
.current 직접 수정 |
| 주요 용도 | 화면에 반영되는 상태 | DOM 접근, 렌더링 무관한 값 저장 |
핵심 기준은 간단합니다. 그 값이 바뀌었을 때 화면이 다시 그려져야 하는가? 그렇다면 useState, 아니라면 useRef입니다.
주의할 점
1. 렌더링 중에 ref를 읽거나 쓰지 말 것
.current 값은 렌더링 과정(컴포넌트 함수 본문)에서 직접 읽거나 수정하지 않는 것이 원칙입니다. 이벤트 핸들러나 useEffect 안에서 다루는 것이 안전합니다.
function Component() {
const ref = useRef(0);
// ❌ 렌더링 중에 ref를 수정하는 것은 예측 불가능한 동작을 유발
ref.current += 1;
// ✅ 이벤트 핸들러나 useEffect 안에서 다루기
const handleClick = () => {
ref.current += 1;
};
return <button onClick={handleClick}>클릭</button>;
}2. ref 값 변경은 화면에 반영되지 않는다
앞서 강조했듯, ref를 수정해도 화면은 자동으로 갱신되지 않습니다. 화면에 보여줘야 하는 값이라면 반드시 useState를 사용해야 합니다.
마무리
useRef는 React에서 두 가지 뚜렷한 역할을 합니다. 첫째, 리렌더링을 유발하지 않으면서 값을 기억하고 싶을 때(타이머 ID, 이전 값 저장 등), 둘째, 실제 DOM 요소에 직접 접근해야 할 때(포커스, 스크롤 등)입니다.
기억할 핵심은 이것입니다. useState는 화면을 위한 상태이고, useRef는 화면 뒤에서 조용히 값을 기억하거나 DOM을 다루기 위한 도구입니다. 이 둘의 차이를 명확히 이해하면 React 컴포넌트를 훨씬 정교하게 제어할 수 있습니다.