React에서 로딩 상태 관리하기: useEffect vs Suspense 비교 🚀
useEffect로 로딩 상태 관리하기 ⚙️
전통적인 방식으로, 컴포넌트가 마운트된 후 데이터를 가져오고 로딩 상태를 수동으로 관리합니다.
function DataCenter({ id }) {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchData() {
setIsLoading(true);
try {
const response = await fetch(`/api/datacenter/${id}`);
const result = await response.json();
setData(result);
} catch (err) {
setError(err);
} finally {
setIsLoading(false);
}
}
fetchData();
}, [id]);
if (isLoading) return <CircularProgress />;
if (error) return <Alert severity="error">에러 발생!</Alert>;
return <DataCenterInfo data={data} />;
}
장점 👍
- 간단한 구현
- 모든 React 버전에서 사용 가능
- 로딩, 에러, 성공 상태를 명시적으로 제어 가능
단점 👎
- 보일러플레이트 코드가 많음
- 여러 데이터 소스를 처리할 때 코드가 복잡해짐
- 수동으로 상태를 관리해야 함
Suspense로 로딩 상태 관리하기 ✨
React의 Suspense는 데이터 로딩을 선언적으로 처리할 수 있게 해주는 기능입니다.
// data-source.js
const fetchData = createResource(async (id) => {
const res = await fetch(`/api/datacenter/${id}`);
return res.json();
});
// component.jsx
function DataCenterPage({ id }) {
return (
<Suspense fallback={<CircularProgress />}>
<DataCenterDetails id={id} />
</Suspense>
);
}
function DataCenterDetails({ id }) {
const data = fetchData.read(id); // 여기서 데이터를 읽으면서 필요시 자동으로 Suspense
return <DataCenterInfo data={data} />;
}
장점 👍
- 선언적 코드 스타일로 가독성 향상
- 중첩된 로딩 상태 처리 가능
- 복잡한 로딩 로직을 컴포넌트에서 분리 가능
단점 👎
- 기존 fetch API와 직접 호환되지 않음
- 추가 설정이나 라이브러리(React Query, SWR 등) 필요
- 여러 Suspense가 중첩될 경우 로딩 UI가 불규칙하게 나타날 수 있음
실제 프로젝트에서의 선택 기준 🧐
- 단순한 애플리케이션: useEffect로 충분
- 복잡한 데이터 흐름 관리: Suspense가 더 적합
- Next.js 사용시: Next.js 13+ 버전은 자체적으로 Suspense 패턴 지원
추가로 알면 좋은 정보 💡
Error Boundary와 Suspense 함께 사용하기
Suspense만으로는 에러 처리가 불가능합니다. Error Boundary와 함께 사용하면 데이터 로딩과 에러 처리를 모두 선언적으로 관리할 수 있습니다.
<ErrorBoundary fallback={<ErrorDisplay />}>
<Suspense fallback={<LoadingSpinner />}>
<DataComponent />
</Suspense>
</ErrorBoundary>
React Query와 함께 사용하기
React Query를 사용하면 Suspense 패턴을 더 쉽게 적용할 수 있습니다.
const { data } = useQuery(['datacenter', id], () => fetchDataCenter(id), {
suspense: true // Suspense 모드 활성화
});
Next.js의 Server Components와 Suspense
Next.js 13 이상에서는 서버 컴포넌트와 함께 Suspense를 활용할 수 있어 데이터 페칭이 더 간결해집니다.
async function DataCenterPage({ id }) {
const data = await fetchDataCenter(id); // 서버에서 직접 데이터 로드
return <DataCenterInfo data={data} />;
}
// 클라이언트에서 사용시
export default function ClientPage({ id }) {
return (
<Suspense fallback={<Loading />}>
<DataCenterPage id={id} />
</Suspense>
);
}
useEffect를 이용하여 로딩 상태 관리하는 방법과 Suspense를 활용하는 방법에 대한 차이점을 설명해주세요.
Suspense와 기존 로딩 상태 관리 방식인 useEffect()와 loading state는 로딩 상태를 관리하는 방식에서 근본적인 차이가 있습니다. 기존 방식에서는 데이터를 불러올 때 useEffect() 훅을 사용하고, 로딩 상태를 관리하기 위해 isLoading이라는 별도의 상태 변수를 만들어야 합니다. 예를 들어, 데이터를 불러오는 동안엔 isLoading을 true로 설정하고, 데이터가 다 불러온다면 false로 바꾸는 식입니다. 그래서 조건에 따라 로딩 UI를 보여주는 식으로 작동합니다. 이 방식은 간단한 상황에서는 충분히 유효하지만, 여러 개의 비동기 데이터를 다룰 때에는 조건부 렌더링 로직이 복잡해질 수 있습니다.
반면, Suspense는 로딩 중인 컴포넌트를 직접 렌더링하지 않고, Suspense 컴포넌트의 fallback 속성으로 로딩 UI를 정의하게끔 합니다. 데이터를 기다리는 동안에는 fallback으로 정의된 UI만 보여주고, 데이터가 모두 준비되면 Suspense에 감싸진 컴포넌트를 자연스럽게 표시합니다. 이렇게 로딩 상태를 선언적으로 관리할 수 있기 때문에, 전체적인 코드가 단순해지고 유지보수도 쉬워집니다.
Suspense의 단점은 무엇일까요? 🤔
여러 개의 Suspense 컴포넌트를 중첩하거나 트리 구조로 사용할 경우, 각 Suspense가 독립적으로 로딩 상태를 관리하기 때문에 데이터 준비 시점이 다를 수 있습니다. 그 결과 로딩 화면(fallback)이 여러 번 표시되거나 비일관적인 UI 경험이 발생할 수 있습니다. 이를 적절히 제어하기 위해서는 트리의 구조와 데이터 로딩 흐름을 신중하게 설계해야 합니다.
또한 Suspense는 Promise 기반의 비동기 작업만 지원합니다. 따라서 일반적인 fetch 요청에 바로 적용할 수 있는 것이 아니라, 이를 위해 추가적인 라이브러리를 사용하거나 Suspense와 호환되는 형태로 Promise를 관리해야 합니다.
'1일 1CS(Computer Science)' 카테고리의 다른 글
localStorage와 sessionStorage의 차이점에 대해서 설명해주세요 (0) | 2025.05.22 |
---|---|
스케일 아웃과 스케일 업의 차이점을 설명해주세요. (0) | 2025.05.21 |
리액트 동시성 모드(Concurrent Mode)에 관해서 설명해주세요. (0) | 2025.05.20 |
ACID에 대해서 설명해주세요. (0) | 2025.05.20 |
리액트에서 컴포넌트가 불필요하게 리렌더링되는 상황을 방지하기 위한 방법을 설명해 주세요. (0) | 2025.05.19 |