React 개발자라면 한 번쯤은 useState와 useEffect를 써봤을 텐데요. 이 훅들과 함께 React의 강력한 훅 중 하나인 useRef는 얼핏 보기엔 간단해 보이지만, 실제로는 컴포넌트의 특정 상황을 관리하는 데 매우 유용하게 쓰입니다.
오늘은 크게 두 가지 메인 사용 방식을 바탕으로 useRef hook의 대해서 알아보도록 하겠습니다.
1. 렌더링과 무관하게 값을 유지할 때
우선, useRef hook은 렌더링과 무관하게 값을 유지할 때 사용됩니다.즉, "값은 변경하지만, 리렌더링을 유발하고 싶지 않을때 쓴다"고 이해하시면 편한데요.
저희가 익숙하게 쓰는 useState로 선언된 변수는 값이 변경되면 컴포넌트가 리렌더링됩니다. 하지만 때로는 리렌더링을 유발하지 않으면서도 컴포넌트 생애주기 동안 값을 유지하고 싶은 경우가 있습니다. 이럴 때 useRef를 사용하면 좋은데요.
import React, { useRef } from 'react';
function CounterWithoutRerender() {
const countRef = useRef(0); // 초기값 0으로 설정
const handleClick = () => {
countRef.current = countRef.current + 1; // .current 속성으로 값에 접근하고 변경
console.log('버튼 클릭 횟수 (useRef):', countRef.current);
// 이 값을 변경해도 컴포넌트는 리렌더링되지 않습니다!
};
return (
<div>
<p>현재 클릭 횟수 (콘솔 확인): {countRef.current}</p>
<button onClick={handleClick}>클릭!</button>
<p>(참고: 위의 숫자는 리렌더링되지 않으므로 업데이트되지 않습니다.)</p>
</div>
);
}
export default CounterWithoutRerender;
useRef는 .{current} 속성을 가진 변경 가능한(mutable) 객체를 반환합니다. 이 current 속성에 값을 저장하면, current 값이 바뀌어도 리렌더링되지 않게 됩니다. 하지만, 그 값은 기억하고 있기 때문에 다른 이유로 컴포넌트가 리렌더링되면 바뀐 값으로 렌더링되게 되죠.
그래서 위의 예제에서 버튼을 클릭하면,
1)내부적으로 숫자는 계속 바뀌지만, 리렌더링이 일어나지않게 됩니다.
2)만약 다른 이유로 리렌더링이 일어나면, 그동안 눌러서 증가시켰던 값이 화면에 표시됩니다.
이게 가능한 이유는 useRef는 React의 내부 상태 관리 시스템이 컴포넌트 인스턴스별로 특정 mutable 객체를 "기억"하고 있으며, 매 렌더링 시에도 그 동일한 객체를 반환해주는 원리를 사용하기 때문입니다.
2. 특정 DOM 요소에 직접 접근할 때
useRef를 사용하는 또 다른 상황은 DOM 요소에 직접 접근할 때 인데요. 사실 React는 가상 DOM(Virtual DOM)을 사용하여 UI를 효율적으로 업데이트합니다. 따라서 대부분의 경우 우리는 DOM에 직접 접근할 필요 없이 React의 상태와 프롭스(props)를 통해 UI를 제어합니다.
하지만 가끔은 특정 DOM 요소에 직접 접근해야 하는 불가피한 상황이 생기는데, 이때 useRef가 큰 역할을 합니다. 예를 들어, 특정 input 요소에 포커스를 주거나, 스크롤 위치를 조작하거나, 비디오 재생을 제어하는 등의 작업이 이에 해당하는데요. 이 경우도 사실 위에서 언급한 "렌더링과 무관하게 값을 유지할 때"와 관련이 있습니다.
import React, { useRef, useEffect } from 'react';
function FocusInput() {
const inputRef = useRef(null); // 초기값은 null
useEffect(() => {
// 컴포넌트가 마운트된 후 inputRef.current는 실제 <input> DOM 요소를 참조합니다.
if (inputRef.current) {
inputRef.current.focus(); // DOM 요소에 직접 focus() 메서드 호출
}
}, []); // 빈 배열은 컴포넌트가 처음 마운트될 때 한 번만 실행됨을 의미
return (
<div>
<input type="text" ref={inputRef} placeholder="여기에 포커스됩니다" />
<p>페이지 로드 시 이 입력 필드에 자동으로 포커스됩니다.</p>
</div>
);
}
export default FocusInput;
코드와 실행과정을 뜯어보며 살펴봅시다.
1) 컴포넌트 함수 첫 실행 (초기 렌더링 시작)
FocusInput() 함수가 호출됩니다. React가 이 컴포넌트를 화면에 처음 그리기 시작하는 시점입니다. React는 이 컴포넌트 인스턴스를 위한 특별한 ref 객체({ current: null } 형태)를 생성하고 inputRef 변수에 할당합니다. 이 ref 객체는 컴포넌트가 리렌더링되어도 계속 동일한 참조를 유지합니다. 이때 inputRef.current의 값은 null입니다.
2) JSX 반환 (가상 DOM 생성)
<div>
<input type="text" ref={inputRef} placeholder="여기에 포커스됩니다" />
<p>페이지 로드 시 이 입력 필드에 자동으로 포커스됩니다.</p>
</div>
다음 순서로 컴포넌트 함수는 위의 JSX를 반환하는데요. React는 이 JSX를 바탕으로 가상 DOM(Virtual DOM*을 생성하거나 업데이트할 준비를 합니다. 이때 <input> 태그에 ref={inputRef} 속성이 있는 것을 확인하고, "아, 이 엘리먼트가 실제 DOM으로 만들어지면 inputRef 객체에 연결해야겠구나!" 하고 기록해 둡니다.
3) 실제 DOM 렌더링 및 ref 연결 (실제 DOM 반영)
React는 가상 DOM의 변경 사항을 브라우저의 실제 DOM 트리에 적용합니다. 즉, <input> 태그를 포함한 HTML 구조가 브라우저 화면에 실제로 그려지기 시작합니다.
이 과정에서 React는 ref 속성에 특별한 useRef 객체가 할당된 <input> 엘리먼트를 발견합니다.React는 방금 생성된 실제 <input> DOM 엘리먼트 자체를 가져와서, inputRef.current 속성에 할당해 줍니다.
이제 inputRef.current는 더 이상 null이 아니라, 화면에 보이는 <input> 태그의 실제 DOM 엘리먼트 객체를 참조하게 됩니다.
4) useEffect 콜백 실행 (DOM 조작)
컴포넌트의 모든 렌더링 작업이 끝나고, 실제 DOM에 변경 사항이 모두 반영된 후에야 useEffect 훅 내부의 콜백 함수가 실행됩니다. 이 시점에서는 이미 React에 의해 inputRef.current에 실제 DOM 엘리먼트가 할당되어 있으므로, 이 조건문은 true가 됩니다.
그래서 inputRef.current가 가리키는 실제 <input> DOM 엘리먼트의 focus() 메서드가 호출됩니다.
→ 요약하자면, 이 모든 과정이 순식간에 일어나면서, 사용자가 페이지를 로드하면 <input> 필드가 나타남과 동시에 자동으로 커서가 깜빡이며 입력 대기 상태가 되는 것입니다.
관련해서 공부하다가 든 질문들이 있는데요.
Q. 그럼 useState는 쓰면 안되나요?
A. ㅇㅇ. useState는 DOM 엘리먼트 자체의 참조를 저장하거나, React의 ref 속성과 연동하여 실제 DOM 엘리먼트를 자동으로 연결해주는 기능이 없기 때문임.
Q. 그럼 이게 렌더링과 무관하게 값을 유지하는 거랑 무슨 상관?
A. FocusInput 컴포넌트가 처음 마운트되어 <input> 태그가 실제 DOM으로 렌더링될 때, inputRef.current에 실제 input DOM 엘리먼트가 할당되는데요. 이후 컴포넌트가 어떤 이유로든 리렌더링되더라도, inputRef.current에 저장된 이 실제 DOM 엘리먼트의 참조는 계속해서 유지됩니다. 즉, 리렌더링될 때마다 새로운 <input> 엘리먼트가 생성되거나, inputRef.current가 null로 초기화되지 않기 때문! 즉, useRef는 리렌더링과 독립적으로 값을 유지하거나 실제 DOM에 접근해야 할 때 사용하는 도구로 생각하시면 될 듯!
이상입니다. 읽어주셔서 감사합니다.
'웹개발 > ReactJS' 카테고리의 다른 글
[React] useEffect hook에 대해 araboza (0) | 2025.06.20 |
---|---|
[React] 메모이제이션으로 성능 최적화하기(useMemo, React.memo, useCallback 완전 비교) (1) | 2025.06.19 |
[React] useReducer hook에 대해 araboza (1) | 2025.06.13 |
[React] React 함수형 컴포넌트의 생명주기에 대해 araboza. (1) | 2025.06.11 |
[React] React에서 불변성이 뭘까? (0) | 2025.06.09 |