React

React useTransition이란? 느린 업데이트를 비동기 전환으로 부드럽게 처리하는 방법

jonbeo 2025. 8. 12. 10:51
반응형

 

 

안녕하세요 😊
이번 글에서는 React 18 이후 도입된 동시성(Concurrency) 관련 Hook인 **useTransition**을 알아보겠습니다.
useTransition덜 중요한 상태 업데이트를 **비동기 전환(transition)**으로 표시하여, 사용자의 타이핑/클릭 같은 긴급한 인터랙션느린 렌더링에 막히지 않도록 도와줍니다.


1) useTransition 한 줄 정의

긴급도 낮은 업데이트를 “전환”으로 표시해, UI가 끊기지 않게 만드는 Hook
반환값: [isPending, startTransition]

  • isPending: 전환 중인지 여부 (로딩 스피너, 로딩 텍스트 등에 활용)
  • startTransition(cb): cb 안의 상태 업데이트를 낮은 우선순위로 실행

2) 언제 쓰면 좋은가요?

  • 검색어 입력과 대용량 리스트 필터링을 동시에 처리할 때
  • 탭 전환 시 무거운 컴포넌트 렌더링이 따라올 때
  • 정렬/필터 버튼 클릭 후 큰 DOM 갱신이 필요한 경우
  • 에디터 타이핑 중 자동 분석/미리보기가 비용 클 때

핵심은 **사용자 입력(긴급)**은 즉시 반영하고,
**무거운 렌더(비긴급)**는 살짝 늦춰 부드러운 UX를 만드는 것입니다.


3) 기본 사용 예제

import { useState, useTransition } from "react";

export default function SearchBox({ items }) {
  const [text, setText] = useState("");
  const [result, setResult] = useState(items);
  const [isPending, startTransition] = useTransition();

  const onChange = (e) => {
    const value = e.target.value;
    // 긴급: 입력값은 즉시 반영
    setText(value);

    // 비긴급: 무거운 필터링은 전환으로 처리
    startTransition(() => {
      const filtered = items.filter((it) =>
        it.toLowerCase().includes(value.toLowerCase())
      );
      setResult(filtered);
    });
  };

  return (
    <>
      <input value={text} onChange={onChange} placeholder="검색어 입력" />
      {isPending && <p>필터링 중...</p>}
      <ul>
        {result.map((it, i) => <li key={i}>{it}</li>)}
      </ul>
    </>
  );
}

 

포인트

  • 입력값(text)은 즉시 업데이트 → 타이핑 지연 없음
  • 리스트 필터링은 전환 처리 → 렌더가 무거워도 입력이 막히지 않음
  • isPending으로 가벼운 로딩 표시 가능

4) 탭 전환에 적용하기

import { useState, useTransition } from "react";
import HeavyTabPanel from "./HeavyTabPanel";

export default function Tabs() {
  const [tab, setTab] = useState("A");
  const [isPending, startTransition] = useTransition();

  const switchTab = (next) => {
    // 클릭 자체는 즉시 반응
    startTransition(() => {
      // 무거운 콘텐츠 렌더는 전환으로
      setTab(next);
    });
  };

  return (
    <div>
      <button onClick={() => switchTab("A")}>탭 A</button>
      <button onClick={() => switchTab("B")}>탭 B</button>
      {isPending && <span>로딩 중…</span>}
      <HeavyTabPanel active={tab} />
    </div>
  );
}

 


5) useDeferredValue와의 차이

  • useTransition: “상태 업데이트” 자체를 낮은 우선순위로 표시. 여러 상태 변경 묶음에 좋음.
  • useDeferredValue: 값의 지연된 버전을 전달. 입력값은 즉시, 무거운 자식은 지연된 값으로 렌더.
    👉 입력 필드 + 무거운 결과 렌더 구도에선 둘 다 유용하며, 상황에 따라 택1 또는 혼용.

6) 실무 팁 & 흔한 실수

  • 전환은 UX를 부드럽게 할 뿐, 연산 자체를 빠르게 하진 않습니다. (알고리즘 최적화는 별도)
  • 과도한 전환 남발 금지: 모든 업데이트를 전환으로 감싸면, 오히려 피드백이 느려 보일 수 있습니다.
  • 전환 중 로딩 UI는 가볍게: 큰 스피너보다 텍스트/스켈레톤이 자연스럽습니다.
  • 네트워크 요청도 가능하지만, 요청 자체의 지연은 별도로 관리(Fetch + 캐시/서스펜스 병행 고려).
  • TypeScript라면 startTransition(() => {...}) 내부에서 상태 타입이 정확한지 체크하세요.

7) 체크리스트

  • 입력/클릭은 즉시 반응하는가?
  • 무거운 렌더/계산은 startTransition으로 낮은 우선순위 처리했는가?
  • 전환 중임을 isPending으로 적절히 표시했는가?
  • 필요하다면 useDeferredValue와 역할을 분리했는가?

✅ 마무리

useTransition은 “사용자는 즉시 반응, 무거운 일은 살짝 뒤로”라는 철학을 코드로 실현하는 Hook입니다.
검색, 탭, 정렬/필터처럼 비용 큰 렌더가 뒤따르는 화면에서 적용하면 즉시 체감되는 UX 개선을 얻으실 수 있습니다. 😊

반응형