[React] Suspense, 비동기 작업의 새로운 접근법

2024. 6. 17. 01:28dev

리액트 Suspense 는 비동기작업이 완료될때까지의 대기상태를 관리하는 컴포넌트이다.

거기다가 fallback 속성에 지정된 로딩 UI를 넣어주면 간편하게 로딩상태를 보여줄수가 있다! 이걸 몰랐을땐 조건부 렌더링을 통해서 로딩 스피너를 보여주곤 했었는데 Suspense를 사용하면 훨씬 깔끔하게 처리할수있다. 서스펜스에 대해 알아보자!! 


 

1. 배경

React Suspense는 React 16.6에서 실험적기능으로 처음 소개되었고, 18부터 정식 출시된 기능이다. 기존에는 로딩상태를 관리하기 위해서 state를 등록하고, 로딩상태냐 아니냐에 따라 조건부 렌더링을 통해 로딩스피너를 보여주는 방식이 일반적이였다. 

이 방법은 복잡한 UI일수록 코드가 길어지고, 유지보수가 어려워질수 있다. 

 

Suspense는 이런 문제를 해결하기 위해서 도입된 개념으로, 비동기 작업이 완료될때까지 UI의 특정 부분을 지연시키고, 완료되면 자동으로 해당 UI를 렌더링시킨다. 

 

2. 장점

1. 비동기작업의 완료 여부를 손쉽게 관리할수있다. 

2. 비동기작업중 로딩스피너, 스켈레톤 같은 대기화면을 쉽게 보여줄수있다. 

3. 필요한 시점에만 코드를 로드하여 초기 로딩시간을 줄여줄수있다. (코드 스플리팅) 

4. 선언형 코드: 비동기작업의 로딩상태를 명령형코드가 아닌, 선언적으로 관리할수 있어서 가독성 및 유지보수가 개선된다. 

 

3. 기존의 isLoading 패턴

기존에는 비동기 작업이 진행중인 동안 로딩상태를 관리하기위해 아래와같은 패턴을 사용했다. 

import React, { useState, useEffect } from 'react';

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

  useEffect(() => {
    fetchData()
      .then(result => {
        setData(result);
        setIsLoading(false);
      })
      .catch(error => {
        console.error('Error fetching data:', error);
        setIsLoading(false);
      });
  }, []);

  return (
    <div>
      {isLoading ? (
        <div>Loading...</div>
      ) : (
        <div>{data}</div>
      )}
    </div>
  );
}

async function fetchData() {
  // 비동기 작업을 수행하는 함수
  return new Promise(resolve => setTimeout(() => resolve('Fetched data'), 2000));
}

export default App;

 

이렇게 명령형으로 코드를 작성했으며, 조건부렌더링을 통해 로딩 전후에 어떤 UI를 보여줄지 결정했다. 

명령형 방식으로 UI를 작성할땐 위와같이 구체적인 단계와 언제 UI가 업데이트 될지 세부적으로 명시를 해줘야한다. 

 

반면에 선언형 방식은 코드가 "무엇을" 수행해야하는지를 선언하는 방식이다. 우리는 원하는 결과를 작성하고, 어떻게 작업을 수행할지는 시스템에게 권한을 넘긴다. 세부적으로 명시해줬던 작업들을 React가 내부적으로 관리해주도록 변경해보자. 

 

4. Suspense를 사용한 선언형 로딩상태 관리 

이제 서스펜스를 사용하여 동일한 기능을 선언적으로 관리해볼것이다. 

import React, { Suspense } from 'react';

const fetchData = () =>
  new Promise(resolve => setTimeout(() => resolve('Fetched data'), 2000));

function DataComponent() {
  const data = fetchData(); // 비동기 함수 호출

  return <div>{data}</div>;
}

function App() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <DataComponent />
      </Suspense>
    </div>
  );
}

export default App;

 

Suspense 컴포넌트로 감싸진 DataComponent 가 fetch결과가 끝나야 렌더링되는 컴포넌트이고, 

그전까지는 fallback에서 지정한 컴포넌트가 렌더링되어 보여진다. 

isLoading 같은 추가적인 상태관리 없이 간편하게 처리할수있게 되었다!

 

5. Suspense 생명주기 및 동작순서 

1. Suspense 컴포넌트의 렌더링 — Suspense 컴포넌트가 렌더링될때, 자식컴포넌트에서 발생하는 비동기 작업을 시작한다. 

2. 비동기 작업 시작 — 자식 컴포넌트에서 비동기작업이 시작되면, Suspense는 이를 감지하고 fallback 속성에 지정된 UI를 보여준다. 

3. 비동기 작업 완료 감지 — 자식컴포넌트에서 반환된 Promise가 완료되면, React는 Suspense가 감싼 자식 컴포넌트를 렌더링하여 실제 데이터를 보여준다. 

 

6. 결론

리액트의 Suspense는 비동기작업을 보다 쉽게 관리하고, Client에게 더 나은 경험을 제공하는 유용한 기능이다. 

복잡해지는 비동기 로직을 단순화할수 있다는게 큰 장점이라 생각한다.