useRef는 DOM 접근, 렌더링 사이의 값 보존, 명령형 API 연결에 쓰여. 세 용도를 섞지 않도록 각각의 모양을 익혀.
DOM 엘리먼트에 접근하기
useRef로 만든 객체를 JSX의 ref에 넘기면 마운트 뒤 .current가 실제 DOM node를 가리켜. 이벤트 핸들러나 effect에서 focus, 측정, scroll 같은 DOM 메서드를 사용할 수 있어.
화면을 다시 그리지 않고 값을 보존하기
타이머 ID, 이전 prop, WebSocket 연결처럼 렌더링 사이에는 남아야 하지만 바뀌어도 화면을 다시 그릴 필요가 없는 값은 ref에 둘 수 있어. .current를 직접 읽고 쓰며 setter는 없어. 화면에 보여 줄 값은 state에 두고 ref와 구분해.
Callback ref로 연결과 해제를 감지하기
DOM node가 붙고 떨어지는 순간에 작업해야 한다면 객체 대신 함수를 ref로 넘겨. React는 연결할 때 node를, 해제할 때 null을 전달해. IntersectionObserver, ResizeObserver, 서드파티 라이브러리를 안전하게 연결할 때 유용해.
렌더링 중에는 함부로 바꾸지 마
Ref 변경은 React에 알려지지 않으므로 렌더링 결과를 결정하는 값으로 쓰면 화면과 실제 값이 어긋날 수 있어. 이벤트와 effect에서 읽고 쓰는 값에 사용해.
State는 화면을 바꾸고 ref는 화면 밖의 값을 기억해. 값이 달라졌을 때 사용자가 보는 UI도 달라져야 한다면 state가 맞아.
이전 값을 기억하는 데도 쓸 수 있어
렌더링 뒤 effect에서 현재 prop을 ref에 저장하면 다음 렌더링에서 이전 값과 비교할 수 있어. 이 비교가 화면을 바꾸면 state가 필요하지만, 로그나 외부 API 동기화 판단에만 쓰인다면 ref로 충분해.
타이머와 연결 객체는 cleanup과 짝지어
Timeout ID나 WebSocket을 ref에 보관하면 새 작업을 시작하기 전에 이전 것을 취소할 수 있어. 컴포넌트가 사라질 때도 남은 타이머와 연결을 정리해 메모리 누수와 늦은 callback을 막아.
Ref 값을 바꿔도 렌더링은 일어나지 않아. 그 값이 화면에 보여야 한다면 state를 쓰고, 렌더링과 무관한 명령형 handle이나 외부 연결만 ref에 남겨.
LazySection의 callback ref가 node에 연결될 때 IntersectionObserver를 만들고 분리될 때 disconnect하도록 고쳐. Observer 객체는 별도 useRef에 저장하고, 섹션을 여러 번 mount와 unmount해 이전 observer가 남지 않는지 확인해. Ref 값을 바꿔도 렌더링 횟수는 늘지 않아야 하며, 화면에 보여야 하는 값은 state로 옮겨야 한다는 차이도 기록해.
Hint
Observer용 useRef<IntersectionObserver | null>와 callback ref의 책임을 나눠. Attach, detach, 다시 attach 세 단계에서 observe와 disconnect 호출 횟수를 확인해.
Progress
Progress is local-only — sign in to sync across devices.