리액트 공식 문서를 참고한 정리 내용 (25.08 기준)
렌더링에 필요하지 않은 값을 참조할 수 있는 Hook이다.
const ref = useRef(initialValue)
컴포넌트의 최상위 레벨에서 useRef를 호출하여 ref를 선언한다.
import { useRef } from 'react';
function MyComponent() {
const intervalRef = useRef(0);
const inputRef = useRef(null);
// ...
initialValueref는 초기값(시작값)을 갖지만, 렌더링 이후에는 React가 더 이상 관여하지 않으므로 개발자가 자유롭게 변경할 수 있는 값이 된다.
currentcurrent에 initialValue가 들어가며, 이후에는 개발자가 마음대로 current 값을 바꿀 수 있다.ref로 달아주면, React가 알아서 DOM 노드를 current에 넣어주고 매 렌더링마다 새 객체가 아니라 똑같은 객체를 계속 돌려준다.ref.current는 그냥 자바스크립트 변수처럼 직접 바꿀 수 있다.state와 달리 바꿔도 컴포넌트가 다시 렌더링되지는 않는다.ref.current 안에 두면 안 된다.ref는 React가 추적하지 않기 때문에 UI가 안 맞아질 수 있다.ref.current를 읽거나 쓰면 동작이 꼬일 수 있다.ref 객체도 두 번 만들어졌다가 하나는 버려진다.useRef는 값을 저장하는 상자 같은 것으로, ref.current에 원하는 값을 넣으면, 컴포넌트가 다시 렌더링되어도 값이 그대로 유지된다.
단, 이 값은 UI를 다시 그리게 만들지 않는다는 점이 state와 큰 차이이다.
const intervalRef = useRef(0);
intervalRef.current 안에 setInterval로 만든 ID를 저장한다.
intervalRef.current = intervalId;
이렇게 저장해두면 나중에 clearInterval(intervalRef.current)로 꺼내 쓸 수 있다.
ref를 사용하면 다음을 보장
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>;
}
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 조작하기 참고!
부모에서 버튼을 눌러 자식의 <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>
</>
);
}