useEffect와 Suspense를 활용한 로딩 상태 관리 차이점
useEffect를 사용하면 API 요청을 직접 수행하고, useState로 로딩 상태를 명시적으로 관리해야 한다.
✅ 코드 예제
import React, { useState, useEffect } from "react";
function DataFetchingComponent() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true); // 요청 시작 시 로딩 상태 활성화
fetch("https://jsonplaceholder.typicode.com/posts/1")
.then((response) => response.json())
.then((json) => {
setData(json);
setLoading(false); // 데이터 로딩 완료
})
.catch((err) => {
setError(err);
setLoading(false); // 오류 발생 시 로딩 종료
});
}, []);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return <div>{data.title}</div>;
}
export default DataFetchingComponent;
useEffect 안에서 비동기 요청을 실행하고, useState를 통해 로딩 상태 직접 관리loading 상태를 명시적으로 ture → false 로 변경하여 UI 렌더링 흐름을 제어함"Loading…" 메시지를 표시함Suspense 보다 렌더링이 효율적이지 못하다. (React가 자동으로 최적화해주지 않음)Suspense는 리액트의 동시성 기능(Concurrent Features)과 함께 사용되며, 비동기 데이터를 요청하는 컴포넌트가 로딩 상태일 때 자동으로 UI를 관리할 수 있다.
→ React.lazy() 또는 React Query와 함께 자주 사용된다.
✅ Suspense + React.lazy()
import React, { Suspense, lazy } from "react";
const LazyComponent = lazy(() => import("./LazyComponent"));
function App() {
return (
<Suspense fallback={<p>Loading component...</p>}>
<LazyComponent />
</Suspense>
);
}
export default App;
lazy()를 사용하여 LazyComponent를 동적으로 로드한다.fallback UI(로딩 메시지)를 자동으로 표시합니다.✅ Suspense + 데이터 로딩 - React Query 활용
import React, { Suspense } from "react";
import { useSuspenseQuery } from "@tanstack/react-query";
function FetchDataComponent() {
const { data } = useSuspenseQuery({
queryKey: ["post"],
queryFn: async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/posts/1");
return response.json();
},
});
return <div>{data.title}</div>;
}
function App() {
return (
<Suspense fallback={<p>Loading data...</p>}>
<FetchDataComponent />
</Suspense>
);
}
export default App;
Suspense가 자동으로 로딩 상태를 관리해주기 때문에 별도로 useState를 사용할 필요가 없다.Relay 같은 데이터 라이브러리와 결합하여 사용하면 효율적인 데이터 관리가 가능useState 상태 관리가 필요하지 않음Relay 같은 라이브러리와 함께 사용해야함| 비교 항목 | useEffect | Suspense |
|---|---|---|
| 로딩 상태 관리 | useState로 수동 관리 | Suspense가 자동 관리 |
| 코드 가독성 | 로딩 상태를 직접 처리해야 하므로 코드가 길어짐 | 더 간결한 코드 작성 가능 |
| 사용 라이브러리 | 기본 React + fetch | React.lazy(), React Query 등과 함께 사용 |
| SSR 지원 | 가능 | 제한적 (React 18에서 개선되었으나 주의 필요) |
| 성능 최적화 | 기본적인 수준 | 동시성 기능과 결합 시 더 부드러운 렌더링 |
| 유지보수 용이성 | 로직이 여러 곳에 분산될 수 있음 | 로딩 UI가 컴포넌트 트리에서 중앙 집중화됨 |
fetch API와 바로 사용할 수 없음Suspense는 기본적으로 Promise를 반환하는 함수와 함께 사용해야함fetch를 사용하려면 직접 Promise 래핑 로직을 구현한다.Suspense는 클라이언트 렌더링(CSR) 환경에서 최적화되어 있으며, SSR에서는 기본적으로 동작하지 않는다.Suspense for SSR이 도입되었지만, 복잡한 설정이 필요하며 완전한 기능을 보장하지 않는다.Suspense for SSR을 사용하거나, Next.js와 같은 프레임워크의 기능을 활용하여 해결한다.Suspense 자체에는 오류 처리 메커니즘이 없다.<ErrorBoundary fallback={<ErrorComponent />}>
<Suspense fallback={<LoadingComponent />}>
<DataFetchingComponent />
</Suspense>
</ErrorBoundary>Suspense의 관리가 어려움Suspense가 중첩된 경우, 로딩 상태 및 오류 처리가 복잡해질 수 있음Suspense에 대한 fallback UI를 개별적으로 관리해야 함Suspense를 피하거나, 상위 Suspense에서 로딩 상태를 일관되게 관리해야한다.SuspenseList를 사용해 다중 Suspense 컴포넌트를 제어 가능하다.단순한 데이터 로딩 : useEffect 사용이 적합 (ex: API 요청 후 수동으로 상태 관리)
복잡한 데이터 요청, 대량의 비동기 처리 :Suspense 사용이 유리 (ex: React Query와 함께 데이터 캐싱 및 관리)
렌더링 성능 최적화 필요 : Suspense가 더 좋은 선택
📚 참고자료