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 개선을 얻으실 수 있습니다. 😊
반응형