C.W.K.
Stream
Lesson 03 of 07 · published

useRef — DOM ref, mutable storage, callback ref

~14 min · useref, ref, mutable, imperative

Level 0React 입문자
0 XP0/54 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
useRef 가 세 가지 함. 각 flavor 마다 자기 근육 기억. 셋을 혼동하는 곳에 버그 살아.

Flavor 1 — DOM node 핸들

Ref 만들고 ref attribute 로 JSX element 에 넘김, 마운트 후 ref 의 .current 가 실제 DOM node 가리킴. 이벤트 핸들러 + effect 에서 DOM 메서드 읽기/호출. 가장 흔한 케이스: 마운트 시 input focus.

Flavor 2 — render-stable mutable storage

Render 간에 살아남되 바뀌어도 re-render 트리거 안 하는 값 필요. 흔한 사용: render 간 타이머 id 캐싱, 이전 prop 값 추적, websocket 연결 저장. 모양 동일 (useRef(initial)) 인데 .current 직접 read/write — setter 없음, re-render 없음.

Flavor 3 — callback ref

가끔 ref 값만 원하는 게 아니라 node 가 attach/detach 될 때 반응 하고 싶음. Ref object 대신 함수를 ref 로 넘김. React 가 attach 시 node 와, detach 시 null 과 함께 호출. 이게 IntersectionObserver, ResizeObserver, 또는 라이프사이클 알아야 하는 서드파티 라이브러리 연결 방식.

중요한 구분

State 는 render 구동. Ref 는 안 함. 값 변경이 UI re-render 해야 하면 state. Render 안 하지만 render 간 기억 필요하면 ref. 값의 job 에 맞는 쪽 골라.

Render 중 .current 읽지 마. React 가 render 중에 .current 에 뭐 있을지 보장 안 함 — DOM ref 는 첫 render 에 null (node 아직 마운트 안 됨), render 중 mutable ref 읽기는 비결정적. 항상 이벤트 핸들러, effect, 또는 `useImperativeHandle` 안에서 읽어.

Code

Flavor 1 — 마운트 시 input focus·tsx
import { useEffect, useRef } from "react";

function AutoFocusInput() {
  const ref = useRef<HTMLInputElement>(null);
  useEffect(() => {
    ref.current?.focus();
  }, []);
  return <input ref={ref} placeholder="Start typing" />;
}
Flavor 2 — re-render 안 하는 mutable storage·tsx
import { useEffect, useRef, useState } from "react";

// '이전 값' hook — 캐논 Flavor-2 사용 케이스.
function usePrevious<T>(value: T): T | undefined {
  const ref = useRef<T | undefined>(undefined);
  useEffect(() => {
    ref.current = value;
  }, [value]);
  return ref.current;
}

function Counter() {
  const [count, setCount] = useState(0);
  const previousCount = usePrevious(count);
  return (
    <div>
      <p>Now: {count}, before: {previousCount ?? "never"}</p>
      <button onClick={() => setCount((n) => n + 1)}>Increment</button>
    </div>
  );
}
Flavor 3 — IntersectionObserver 용 callback ref·tsx
import { useCallback } from "react";

function LazySection({ children }: { children: React.ReactNode }) {
  // useCallback 이 함수 안정적으로 유지해서 React 가 매 render 마다 호출 안 함.
  const setRef = useCallback((node: HTMLDivElement | null) => {
    if (!node) return; // null = detach; 할 것 없음
    const observer = new IntersectionObserver(([entry]) => {
      if (entry.isIntersecting) console.log("In view");
    });
    observer.observe(node);
    // (실제 앱에선 cleanup 도 필요 — exercise 참조.)
  }, []);

  return <div ref={setRef}>{children}</div>;
}

External links

Exercise

LazySection 의 callback ref 를 cleanup 제대로 처리하게 개선. Node detach 시 IntersectionObserver disconnect 필요. Hint: observer 를 useRef (Flavor 2) 에 저장해서 callback 이 attach 시 생성 + detach 시 disconnect 둘 다 가능. 컴포넌트 동적으로 삽입/제거해서 동작 확인.
Hint
Ref 둘: observer 용 (useRef<IntersectionObserver | null>(null)), 또 하나 callback ref. Attach 시 생성 + observe + 저장. Detach 시 저장된 observer 읽어서 disconnect.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.