DYO 공부하는 블로그
[React] useRef 훅 정리 본문
리액트의 ref는 DOM 요소를 직접 제어하도록 자주 사용된다 (비제어 컴포넌트) 하지만 단순한 DOM 제어를 넘어서 렌더링과 독립된 값 저장소로도 자주 사용된다. ref의 다양한 활용 예시를 보면서 ref의 활용법을 알아보자.
1. 작성 위치와 제어 주체에 따라
1. 컴포넌트 내의 자식이나 DOM을 직접 참조할 때
export function CustomPage(){
const myRef = useRef();
return <input ref={myRef} />
}
이 경우에는 같은 컴포넌트 내부의 DOM 요소를 ref로 참조하고자 할 때 사용한다.
2. 자식 컴포넌트 내부의 DOM을 참조하고자 할 때
export function CustomPage(){
const myRef = useRef();
return <CustomComponent ref={myRef} />
}
const CustomComponent = React.forwardRef((props, ref) => {
return <input ref={ref} />;
});
이 경우에는 자식 컴포넌트의 DOM요소를 참조한다.
3. 자식 컴포넌트 내부의 핸들러를 상위 요소로 전달하고자 할 때
- 하위 요소에서 useImperativeHandle로 메서드 객체 보내기
export inner = React.forwardRef((props, ref) => {
const inputRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current?.focus();
},
}));
return <input type="text" ref="ref"/>
})
- 상위 요소에서 하위 요소의 메서드 받아오기
export const outer = () => {
const innerRef = useRef();
return (<Inner ref={innerRef}> />
<button onclick={() => innerRef.focus()}> 포커스 </button>
}
분리된 하위 컴포넌트의 메서드를 사용해야 할 때 하위 컴포넌트의 메서드를 가져와 사용할 수 있다.
이런 합당한 의문이 생길 수 있다.
상위 요소에서 로직이 관리되지 않는 고수준 컴포넌트에서 하위 요소의 핸들러를 왜 호출해야 하지? 컴포넌트의 상태로 관리되면 되는 것 아닌가?
하지만 useImperativeHandle이 필요한 때가 있다. 자체 상태를 관리하면서도 외부 명령을 받을 수 있어야 하는 때이다. 주로 사용되는 경우는 다음과 같다.
- 모달 / 툴팁 등의 imperative API가 필요한 UI : 모달 자체를 상위 요소에서 컨트롤하고자 할 때
- Canvas, WebGL, 오디오 재생 등의 impretive interface : 렌더링 이외의 외부 리소스를 다루는 컴포넌트들
- 외부 라이브러리 래핑 컴포넌트 : chart.js, three.js 등의 내부 인스턴스를 직접 expose해야 할 때.
또한 useImperativeHandle은 선택적으로 사용할 수 있기 때문에 ( 반드시 ref를 넘겨 내부 핸들러를 받아야 하는 것이 아님. ) 상위 요소에서 필요한 핸들러들을 지정해두고, 사용하지 않는 상위 요소에서는 하위 요소의 지정된 ref를 몰라도 된다는 점이다.
2. 렌더링 트리거 중심으로
useRef는 컴포넌트 내부의 값을 저장할 수 있다는 점에서 useState와 비슷하지만 이 둘은 '렌더링 트리거 여부'라는 결정적 차이가 있다.
useRef는 값이 바뀌더라도 리렌더링이 일어나지 않는다.(렌더링 트리거 없음) 그러나 useState는 setState함수로 값을 변경하는 순간 리렌더링이 발생한다. (렌더링 트리거 동작)
그래서 useRef는 렌더링과 무관한 값을 캐싱할 때, useState는 렌더링을 트리거해야 할 때 사용한다.
1. 메모이제이션처럼 동작할 수 있다.
- 값이 저장되지 않는 예시
export function Poo(){
let i = 0;
const [test, setTest] = useState()
return (<button type="button">{i++}</button>
<button type="button" onClick(() => setTest())>)
}
위 예시의 경우에는 Poo 컴포넌트에 대해서 리렌더링이 일어날 경우 i값이 초기화된다.
- 값이 저장되는 예시
export function Poo(){
const i = useRef(0);
const [test, setTest] = useState()
return (<button type="button">{i.current.value = i + 1}</button>
<button type="button" onClick(() => setTest())>)
}
위 예시의 경우에는 Poo 컴포넌트에 대해 렌더링과 무관하게 i값이 유지된다.
위와 같은 특징으로 인해서 렌더링에 자원이 많이 필요한 오디오 데이터, 이미지 프리뷰 데이터 등과 같은 데이터의 이전 값을 저장하고 싶을 때 사용할 수 있다. ( 캐싱처럼 )
'React' 카테고리의 다른 글
| 프론트의 로깅은 무엇일까? 로깅을 알아보자 (0) | 2025.09.11 |
|---|---|
| [Zustand] Root 페이지의 오버레이 통제와 subscribeWithSelector (0) | 2025.08.25 |
| [Zustand] Zustand 기본 사용법, v5 변경점 (0) | 2025.08.20 |
| Presentational-Container 패턴과 Hook (1) | 2025.08.10 |
| [React] React 유용한 스니펫들 정리 (0) | 2025.07.08 |