Frontend Development

React 로딩 상태 관리: useEffect vs Suspense

Kun Woo Kim 2025. 5. 29. 00:24
반응형

프론트엔드 개발에서 데이터 로딩 상태를 관리하는 것은 매우 중요한 부분입니다. 이 글에서는 전통적인 useEffect를 이용한 방식과 React의 새로운 기능인 Suspense를 활용한 방식을 비교해보겠습니다.


useEffect를 이용한 로딩 상태 관리

기본 구현 방식

function UserProfile() {
  const [data, setData] = useState(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        setIsLoading(true);
        const response = await fetch('/api/user');
        const result = await response.json();
        setData(result);
      } catch (err) {
        setError(err);
      } finally {
        setIsLoading(false);
      }
    };

    fetchData();
  }, []);

  if (isLoading) return <LoadingSpinner />;
  if (error) return <ErrorMessage error={error} />;
  return <UserInfo data={data} />;
}

장점

  • 직관적인 구현 방식
  • 기존 React 지식으로 쉽게 이해 가능
  • 세밀한 에러 처리 가능

단점

  • 보일러플레이트 코드가 많음
  • 여러 비동기 작업 시 복잡한 조건부 렌더링
  • 로딩 상태 관리 로직이 컴포넌트와 강하게 결합

Suspense를 이용한 로딩 상태 관리

기본 구현 방식

// 데이터 페칭을 위한 리소스 생성
function createResource(asyncFn) {
  let status = 'pending';
  let result;
  let suspender = asyncFn().then(
    (data) => {
      status = 'success';
      result = data;
    },
    (error) => {
      status = 'error';
      result = error;
    }
  );

  return {
    read() {
      if (status === 'pending') {
        throw suspender;
      } else if (status === 'error') {
        throw result;
      } else if (status === 'success') {
        return result;
      }
    }
  };
}

// 컴포넌트에서 사용
function UserProfile() {
  const resource = useMemo(() => createResource(
    () => fetch('/api/user').then(res => res.json())
  ), []);

  return (
    <Suspense fallback={<LoadingSpinner />}>
      <UserInfo data={resource.read()} />
    </Suspense>
  );
}

장점

  • 선언적인 로딩 상태 관리
  • 컴포넌트 코드가 더 깔끔해짐
  • 로딩 UI를 중앙에서 관리 가능

단점

  • Promise 기반 비동기 작업만 지원
  • 중첩된 Suspense 사용 시 복잡한 로딩 상태 관리
  • 추가적인 리소스 관리 로직 필요

두 방식의 주요 차이점

1. 상태 관리 방식

  • useEffect: 명령형(Imperative) 방식
    • 로딩 상태를 직접 관리
    • 조건부 렌더링으로 UI 제어
  • Suspense: 선언형(Declarative) 방식
    • 로딩 상태를 컴포넌트 외부에서 관리
    • fallback UI로 자연스러운 전환

2. 코드 구조

  • useEffect:
    if (isLoading) return <LoadingSpinner />;
    return <ActualContent />;
  • Suspense:
    <Suspense fallback={<LoadingSpinner />}>
      <ActualContent />
    </Suspense>

3. 사용 사례

  • useEffect:
    • 단순한 데이터 페칭
    • 세밀한 에러 처리 필요
    • 기존 코드베이스와의 통합
  • Suspense:
    • 여러 비동기 작업 동시 처리
    • 코드 분할과 함께 사용
    • 새로운 프로젝트 시작

실제 사용 시 고려사항

1. Suspense 사용 시 주의점

  • 중첩된 Suspense 컴포넌트 사용 시 로딩 UI가 여러 번 표시될 수 있음
  • 데이터 준비 시점이 다를 수 있어 비일관적인 UI 경험 발생 가능
  • Promise 기반 작업만 지원하므로 추가 라이브러리 필요할 수 있음

2. useEffect 사용 시 주의점

  • 여러 비동기 작업 시 복잡한 상태 관리 필요
  • 컴포넌트가 언마운트된 후 상태 업데이트 시 메모리 누수 가능성
  • 로딩 상태 관리 로직이 컴포넌트와 강하게 결합

결론

  1. useEffect 선택 시기

    • 기존 프로젝트 유지보수
    • 세밀한 에러 처리 필요
    • 단순한 데이터 페칭
  2. Suspense 선택 시기

    • 새로운 프로젝트 시작
    • 여러 비동기 작업 동시 처리
    • 코드 분할과 함께 사용

각 방식은 장단점이 있으므로, 프로젝트의 요구사항과 상황에 맞게 선택하는 것이 중요합니다. Suspense는 React의 미래를 대표하는 기능이지만, 아직 실험적 기능이므로 프로덕션 환경에서 사용 시 신중한 검토가 필요합니다.


참고 자료

반응형