
🤓: "useRef란 무엇인가요?"
👀: "React에서 DOM 요소에 직접 접근할 때 쓰는 hook입니다."
라고 면접 질문 답변식으로만 useRef를 이해하고 있었던 나..
실제로 업무할때도 기존의 코드를 참고해서 비슷한 방식으로 코드를 짜다보니 제대로 그 개념에 대해 이해하고 있지 못한채 사용하고 있는 것 같아 useRef, 그리고 그와 함께 자주 사용되는 forwardRef, useImperativeHandle에 대해 공부해보았다.
useRef는 React에서 제공하는 훅(Hook) 중 하나이다. 말 그대로 reference를 만들 때 사용하는데, 주로 다음과 같은 상황에서 쓰인다:
1. DOM 요소에 직접 접근할 때
2. 렌더링과 관계없는 값을 저장할 때 (리렌더링을 발생시키지 않음)
import { useRef, useEffect } from 'react';
function MyComponent() {
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus(); // 컴포넌트 마운트 시 자동 포커싱
}
}, []);
return <input ref={inputRef} />;
}
function Timer() {
const count = useRef(0);
useEffect(() => {
const interval = setInterval(() => {
count.current += 1;
console.log('현재 카운트:', count.current);
}, 1000);
return () => clearInterval(interval);
}, []);
return <div>콘솔을 확인해보자!</div>;
}
이 예제처럼 useRef는 값이 바뀌어도 리렌더링되지 않는다. 그래서 단순히 값을 저장하거나 유지하는 용도로도 자주 사용된다.
forwardRef는 부모 컴포넌트가 자식 컴포넌트의 DOM 요소나 내부 인스턴스에 ref를 전달할 수 있도록 해주는 React의 기능이다.
기본적으로 ref는 HTML 요소에만 바로 전달되지만, 커스텀 컴포넌트에는 기본적으로 전달되지 않는다. 그걸 해결해주는 게 forwardRef이다.
import { forwardRef } from 'react';
const MyInput = forwardRef<HTMLInputElement, React.ComponentProps<'input'>>((props, ref) => {
return <input {...props} ref={ref} />;
});
이제 이 MyInput 컴포넌트는 외부에서 ref를 받아서 내부의 input에 전달해줄 수 있게 된다.
function Parent() {
const inputRef = useRef<HTMLInputElement>(null);
const focusInput = () => {
inputRef.current?.focus();
};
return (
<>
<MyInput ref={inputRef} />
<button onClick={focusInput}>포커스 주기</button>
</>
);
}
여기서 조금 헷갈린 부분이 있다.
"ref는 어디서 쓰는거고 forwardRef는 또 어디서 쓰는거지?"
forwardRef는 자식 컴포넌트를 선언할 때 사용하고,ref는 부모 컴포넌트에서 사용한다.
| 역할 | 사용하는 위치 |
|---|---|
forwardRef | 자식 컴포넌트를 선언할 때 사용 |
ref | 부모 컴포넌트에서 사용 |
즉, forwardRef는 자식이 외부에서 ref를 받을 준비를 하는 거고,
부모는 그냥 ref를 달아주기만 하면 되는 구조다.
가끔은 ref를 단순히 DOM 요소에만 전달하는 게 아니라, 컴포넌트 외부에서 내부 메서드를 사용할 수 있도록 하고 싶을 때도 있다.
그럴 땐 useImperativeHandle과 같이 사용하면 된다.
import { forwardRef, useImperativeHandle, useRef } from 'react';
const FancyInput = forwardRef((_, ref) => {
const inputRef = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current?.focus();
},
clear: () => {
if (inputRef.current) {
inputRef.current.value = '';
}
},
}));
return <input ref={inputRef} />;
});
function Parent() {
const fancyRef = useRef<{ focus: () => void; clear: () => void }>(null);
return (
<>
<FancyInput ref={fancyRef} />
<button onClick={() => fancyRef.current?.focus()}>포커스</button>
<button onClick={() => fancyRef.current?.clear()}>클리어</button>
</>
);
}
이렇게 하면 외부에서 컴포넌트 내부의 특정 동작을 제어할 수 있다.
useRef와 forwardRef는 특히 사용자 정의 input이나 modal, focus 컨트롤이 필요한 UI에서 자주 쓰이니 잘 익혀두었다가 유용하게 사용하자!