들어가며
React 애플리케이션을 개발하다 보면 무거운 작업으로 인해 UI가 버벅이는 경험을 해보셨을 거예요.
오늘은 이런 문제를 해결할 수 있는 useTransition
훅에 대해 자세히 알아보겠습니다! 😊
useTransition이란? 🤔
useTransition
은 우선순위가 낮은 상태 업데이트를 표시하는 React 훅입니다.
이 훅은 두 가지 값을 반환합니다:
isPending
: 전환이 진행 중인지 여부startTransition
: 우선순위가 낮은 업데이트를 시작하는 함수
기본 사용법 📝
import { useTransition } from 'react';
function SearchComponent() {
const [isPending, startTransition] = useTransition();
const [searchQuery, setSearchQuery] = useState('');
const [searchResults, setSearchResults] = useState([]);
const handleSearch = (e) => {
const value = e.target.value;
// 즉시 입력값 업데이트 (높은 우선순위)
setSearchQuery(value);
// 검색 결과 업데이트는 낮은 우선순위로 처리
startTransition(() => {
setSearchResults(searchItems.filter(item =>
item.toLowerCase().includes(value.toLowerCase())
));
});
};
return (
<div>
<input
value={searchQuery}
onChange={handleSearch}
placeholder="검색어를 입력하세요"
/>
{isPending ? (
<div>검색 결과 업데이트 중...</div>
) : (
<SearchResults results={searchResults} />
)}
</div>
);
}
실전 활용 예시 💡
1. 대량 데이터 필터링
function DataGrid({ items }) {
const [isPending, startTransition] = useTransition();
const [filters, setFilters] = useState({});
const [filteredItems, setFilteredItems] = useState(items);
const updateFilters = (newFilters) => {
// 필터 UI 즉시 업데이트
setFilters(newFilters);
// 데이터 필터링은 낮은 우선순위로
startTransition(() => {
setFilteredItems(
items.filter(item => {
return Object.entries(newFilters).every(([key, value]) =>
item[key].toString().includes(value)
);
})
);
});
};
return (
<div>
<FilterControls
filters={filters}
onUpdateFilters={updateFilters}
/>
{isPending ? (
<DataGridSkeleton />
) : (
<DataGridContent items={filteredItems} />
)}
</div>
);
}
2. 탭 전환 애니메이션
function TabPanel() {
const [isPending, startTransition] = useTransition();
const [activeTab, setActiveTab] = useState('info');
const [tabContent, setTabContent] = useState(null);
const handleTabChange = (tab) => {
// 탭 하이라이트 즉시 변경
setActiveTab(tab);
// 컨텐츠 로딩은 transition으로 처리
startTransition(async () => {
const content = await loadTabContent(tab);
setTabContent(content);
});
};
return (
<div className="tab-container">
<TabButtons
activeTab={activeTab}
onTabChange={handleTabChange}
/>
<div className={`tab-content ${isPending ? 'loading' : ''}`}>
{isPending ? (
<FadeTransition>
<LoadingSpinner />
</FadeTransition>
) : (
<FadeTransition>
{tabContent}
</FadeTransition>
)}
</div>
</div>
);
}
3. 무한 스크롤 구현
function InfiniteList() {
const [isPending, startTransition] = useTransition();
const [items, setItems] = useState([]);
const [page, setPage] = useState(1);
const loadMoreItems = async () => {
// 로딩 상태 표시
setPage(prev => prev + 1);
startTransition(async () => {
const newItems = await fetchItems(page);
setItems(prev => [...prev, ...newItems]);
});
};
return (
<div>
<ItemList items={items} />
{isPending ? (
<LoadingRow />
) : (
<LoadMoreButton onClick={loadMoreItems} />
)}
</div>
);
}
고급 활용 기법 🔥
1. 커스텀 훅으로 재사용
function useTransitionQuery(queryFn) {
const [isPending, startTransition] = useTransition();
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const execute = useCallback((params) => {
startTransition(async () => {
try {
const result = await queryFn(params);
setData(result);
setError(null);
} catch (err) {
setError(err);
setData(null);
}
});
}, [queryFn]);
return {
isPending,
data,
error,
execute
};
}
// 사용 예시
function SearchComponent() {
const search = useTransitionQuery(searchAPI);
return (
<div>
<input onChange={e => search.execute(e.target.value)} />
{search.isPending ? "검색 중..." : search.data}
{search.error && <ErrorMessage error={search.error} />}
</div>
);
}
2. 중첩된 트랜지션 처리
function NestedTransitions() {
const [isPending1, startTransition1] = useTransition();
const [isPending2, startTransition2] = useTransition();
const handleComplexUpdate = () => {
// 첫 번째 무거운 업데이트
startTransition1(() => {
setFirstData(computeExpensiveData());
// 두 번째 무거운 업데이트
startTransition2(() => {
setSecondData(computeMoreExpensiveData());
});
});
};
return (
<div>
{isPending1 && <LoadingIndicator level={1} />}
{isPending2 && <LoadingIndicator level={2} />}
<button onClick={handleComplexUpdate}>업데이트</button>
</div>
);
}
성능 최적화 팁 ⚡️
- 트랜지션 범위 최소화
// 좋은 예 startTransition(() => { setSearchResults(newResults); });
// 피해야 할 예
startTransition(() => {
setSearchResults(newResults);
setOtherState(newValue); // 불필요한 포함
expensiveOperation(); // 여기에 있으면 안 됨
});
2. **적절한 로딩 상태 표시**
```jsx
function LoadingState({ isPending, children }) {
return (
<div className={isPending ? 'opacity-50' : ''}>
{children}
{isPending && (
<div className="loading-overlay">
<Spinner />
</div>
)}
</div>
);
}
주의사항 ⚠️
- 모든 상태 업데이트에 사용하지 않기
- 간단한 업데이트는 일반적인 방식으로
- 실제로 무거운 작업에만 사용
- 비동기 작업 처리
startTransition
내부의 비동기 작업은 적절히 에러 처리- 필요한 경우 cleanup 함수 구현
- 컴포넌트 언마운트 고려
useEffect(() => { let mounted = true; startTransition(() => { heavyOperation().then(result => { if (mounted) { setState(result); } }); }); return () => { mounted = false; }; }, []);
마치며
useTransition
은 React 애플리케이션의 사용자 경험을 크게 개선할 수 있는 강력한 도구입니다.
하지만 모든 상황에서 필요한 것은 아니니, 적절한 상황에 현명하게 사용하시기 바랍니다!
다음 시간에는 React 19의 다른 새로운 훅들에 대해 알아보도록 하겠습니다.
즐거운 코딩 되세요! 🚀
'React.js' 카테고리의 다른 글
[React 19] useOptimistic으로 낙관적 UI 구현하기 - 좋아요 버튼 UX 개선하기 (0) | 2025.03.17 |
---|---|
[React 19] useActionState로 서버 상태 관리하기 (0) | 2025.03.17 |
[React] 렌더링 최적화하기 - React.memo 현명하게 사용하기 (0) | 2025.03.17 |
[React] 웹 페이지 복귀, 이탈 탐지 (가시성) (0) | 2023.06.28 |
React 앱을 Vite + gh-pages로 빌드 및 배포하기 (0) | 2023.06.17 |