자꾸만 익숙했던 방식대로 querySelector
를 사용하려고 하는 스스로를 설득하기 위해 리액트 useRef
훅의 내용을 정리합니다.
간단하게 먼저 결론부터 언급하자면, useRef
를 사용하는 주된 이유는 React의 선언적이고 반응형인 특성과 잘 맞기 때문입니다.
선언적 프로그래밍에 잘 맞는다
React는 선언적 프로그래밍 패러다임을 따릅니다. UI의 현재 상태를 선언하고, 데이터가 변경될 때마다 React가 UI를 자동으로 업데이트 해줍니다. 이 때
useRef
를 사용하면 React의 랜더링 사이클과 일관성을 유지하면서 DOM 요소에 접근할 수 있습니다.document.querySelector
등 Element 요소를 반환하는 방식을 사용하면, 명령적인 코드가 되기 때문에 React의 선언적 특성과 어울리지 않습니다.
렌더링 사이클과 통합된다
useRef
를 사용하면 React의 렌더링 사이클에 통합됩니다. 그래서 컴포넌트가 렌더링될 때 자동으로 참조가 업데이트 됩니다. 결과적으로 DOM요소가 변경될 때마다 안정적으로 참조를 유지할 수 있게 됩니다. 리액트 렌더링 사이클인 마운팅 -> 업데이트 -> 언마운팅의 과정이 있는데,useRef
를 통해 참조된 객체는 이 과정과 라이프 사이클이 동기화 됩니다.document.querySelector
를 사용하면 리액트 라이프사이클과 동기화가 어긋날 수 있어 문제가 생길 수 있습니다. 만약 라이프사이클을 통한 DOM 업데이트 시점과 실제 React 렌더링 라이프사이클을 통해 업데이트 되는 시점이 다를 경우 dom을 찾을 수 없거나, 제대로 업데이트 되지 않은 DOM 요소를 찾을 수도 있기 때문입니다. 이런 경우 컴포넌트가 업데이트(=리렌더링)될 때마다 수동으로 DOM 요소를 다시 찾아야 할 필요가 생깁니다.
메모리 누수 방지
렌더링 사이클 통합과 이어지는 장점으로, React 컴포넌트가 언마운트될 때,
useRef
를 통해 생성된 참조도 자동으로 정리가 되기 때문에 메모리 누수에 대해 안전합니다.document.querySelector
로 가져온 참조의 경우 더이상 활용하지 않게된 Element 를 변수에 가지고 있거나 계속 참조하여 가지고 있음으로 인해 메모리 낭비가 커지기 쉽습니다.
리액트 16.8 부터 적용된 함수 컴포넌트와의 호환
useRef
는 React의 훅 중 하나로, 함수 컴포넌트 내에서 상태나 DOM 요소에 대한 참조를 유지하는데 사용됩니다. 클래스 컴포넌트에서는this.refs
를 통해서 사용했던 기능이지만, 16.8버전부터 생겨난 함수 컴포넌트에서는useRef
가 동일한 역할을 해줍니다.
예시를 통해 차이를 알아보기
document.querySelector
사용 케이스import React, { useEffect } from 'react'; function AutofocusInput() { useEffect(() => { // 컴포넌트가 마운트된 후 input 요소를 선택하여 포커스를 줌 const inputElement = document.querySelector('#myInput'); if (inputElement) { inputElement.focus(); } }, []); return ( <input id="myInput" type="text" /> ); }
이 예시에서 컴포넌트가 마운트 된 후
useEffect
내부에서document.querySelector
를 사용해 DOM요소를 찾고 포커스를 주고 있습니다.
- 이 방법은 작동하지만, React의 선언전 특성과 잘 어울리지 않습니다.
- 또한 ID를 기반으로 요소를 찾기 때문에 ID가 중복되거나 변경될 경우 예상 못한 동작이 발생할 수 있습니다.
- 그리고 리액트 렌더링 사이클과 동기화 되지 않기 때문에 만약 리렌더링이 발생되어 요소에 변화가 생기면 참조가 유효하지 않게 될 가능성도 있습니다.useRef
사용 케이스import React, { useEffect } from 'react'; function AutofocusInput() { useEffect(() => { // 컴포넌트가 마운트된 후 input 요소를 선택하여 포커스를 줌 const inputElement = document.querySelector('#myInput'); if (inputElement) { inputElement.focus(); } }, []); return ( <input id="myInput" type="text" /> ); }
이 예시에서
useRef
를 사용하면 React의 선언적 패러다임을 유지하면서도 렌더링 사이클과의 통합을 보장합니다.
또한 컴포넌트의 렌더링 사이클과 자연스럽게 통합되어있기 때문에 컴포넌트의 상태 변화나 리렌더링에 영향을 받지 않고 일관된 참조를 유지할 수 있습니다.
마치며
왜 useRef
를 사용해야 하는지 정리하며, 리액트의 프로그래밍 철학, 랜더링 동작원리, 메모리까지 연관 되었으므로 꼭 사용해야 된다는 점을 돌이켜 볼 수 있었습니다. 이 글을 보신 분들에게도 지식을 상기하거나 새롭게 아는데 도움이 되었으면 좋겠습니다.