순수 자바스크립트에서는 특정 태그를 조작할 때 document.getElementById나 querySelector 같은 DOM API를 직접 호출했다.
하지만 React는 가상 DOM을 기반으로 선언적 렌더링을 수행하기 때문에, 개발자가 브라우저 DOM을 직접 수정하는 것은 지양해야 한다. 그럼에도 불구하고 실제 DOM 노드에 직접 접근해야 하는 순간이 존재하며, 이때 사용하는 훅이 바로 useRef다.
React의 선언적 상태 관리만으로 처리하기 힘든 명령형 작업에 사용한다.
focus를 주거나 해제할 때offsetWidth, offsetHeight)나 화면상 위치(getBoundingClientRect)를 계산할 때useRef는 { current: initialValue } 형태의 변경 가능한 객체를 반환한다.
import { useRef } from "react";
export default function TextInput() {
// 1. ref 객체 생성 (초기값 null)
const inputRef = useRef(null);
const handleFocus = () => {
// 3. ref.current를 통해 실제 DOM 노드에 접근 후 DOM API 호출
inputRef.current?.focus();
};
// 2. 접근하고자 하는 JSX 엘리먼트에 ref 속성으로 연결
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={handleFocus}>입력창으로 포커스 이동</button>
</div>
);
}
inputRef.current 프로퍼티에 해당 DOM 엘리먼트의 참조값이 할당된다.current 값은 다시 null로 정리된다.기본 브라우저의 <input type="file" />은 디자인을 커스텀하기 매우 까다롭다. 실무에서는 파일 인풋을 화면에서 숨겨두고, 별도로 디자인된 버튼이나 이미지를 클릭했을 때 인풋의 파일 탐색기를 대신 띄우는 패턴을 많이 사용한다.
import { useRef } from "react";
export default function FileUpload() {
const fileInputRef = useRef(null);
// 버튼이나 이미지를 눌렀을 때 숨겨진 file input을 대신 클릭해 주는 핸들러
const handleFileTrigger = () => {
fileInputRef.current?.click();
};
const handleFileChange = (event) => {
const file = event.target.files?.[0];
if (file) {
console.log("선택된 파일:", file.name);
}
};
return (
<div>
{/* 1. 실제 파일 인풋은 화면에서 숨김 처리 */}
<input
ref={fileInputRef}
type="file"
hidden
onChange={handleFileChange}
/>
{/* 2. 클릭 트리거 역할을 할 커스텀 UI */}
<img
src="/preview-placeholder.png"
alt="업로드 미리보기"
onClick={handleFileTrigger}
style={{ width: "200px", cursor: "pointer" }}
/>
<button type="button" onClick={handleFileTrigger}>
이미지 등록하기
</button>
</div>
);
}
fileInputRef.current.click()을 호출하면 사용자가 실제 숨겨진 <input type="file" />을 누른 것과 완전히 동일하게 파일 탐색기 창이 열린다.useRef는 DOM 참조뿐만 아니라 렌더링을 유발하지 않는 컴포넌트 내부 변수를 저장할 때도 사용된다.
useState: 값이 변경되면 컴포넌트가 리렌더링된다. 화면에 직접 표시되는 UI 데이터에 사용한다.useRef: ref.current 값이 바뀌어도 컴포넌트는 리렌더링되지 않는다.setTimeout 타이머 ID, 이전 렌더링 값 추적 등)을 저장할 때 적합하다.
감사합니다 ㅎㅎ이해가잘되었어용