[TIL #42] React - <Suspense> 란?

JMinkyoung·2024년 1월 6일
0

TIL

목록 보기
42/42
post-thumbnail

회사에서는 최신 버전의 React를 사용할 일이 없어서 잘 몰랐는데 R3F 강의를 듣다가
React 18 버전부터 제공하는 Suspense를 이용해서..~ 라는 강사님의 한마디에 찾아보게 되었다.

Suspense 란?

트리 하단에 있는 컴포넌트가 아직 렌더링할 준비가 되지 않은 경우 (아직 로딩중인 경우) fallback을 이용하여 Loading indicator를 나타낼 수 있도록 해준다.
React 공식문서

기존에 React에서 API 호출을 기다리는 동안에 로딩 페이지를 보여주고 싶을때 (비동식) 나는 아래와 같은 방식으로 구현했었다.

App.js

import ImageList from "./ImageList";

export default function App() {
  return (
    <>
      <ImageList />
    </>
  );
}

ImageList.jsx

import { useEffect, useState } from "react";

const ImageList = () => {
  const [isLoading, setIsLoading] = useState(true);
  const [images, setImages] = useState([]);

  const delay = (timeToDelay) =>
    new Promise((resolve) => setTimeout(resolve, timeToDelay));

  useEffect(() => {
    fetchImages()
      .then((data) => setImages(data))
      .then(() => setIsLoading(false));
  }, []);

  const fetchImages = async () => {
    // 3초 후에 이미지를 fecth 하도록
    await delay(3000);
    const res = await fetch(
      "https://api.thecatapi.com/v1/images/search?limit=10"
    );
    return res.json();
  };

  if (isLoading) return <div>Waiting for Loading Images...</div>;

  return (
    <div className="wrapper">
      {images.map((data) => {
        return <img key={data.id} src={data.url} />;
      })}
    </div>
  );
};

export default ImageList;

3초 (임의로 설정한 API 딜레이 시간) 동안은 로딩 컴포넌트가 화면에 노출이 되고,

그 3초가 지나면 받아온 고양이 이미지들이 화면에 그려지게 된다.

이때 나는 isLoading 이라는 state를 이용해 API 호출이 끝났는지 여부를 따로 저장하여 로딩페이지를 보여줄지 고양이 페이지를 보여줄지 여부를 판단하였다.

위 코드를 <Suspense> 를 사용하여 다시 구현하면 아래와 같이 구현 할 수 있다.

wrapPromise.js

function wrapPromise(promise) {
  let status = "pending";
  let response;

  const suspender = promise.then(
    (res) => {
      status = "success";
      response = res;
    },
    (err) => {
      status = "error";
      response = err;
    }
  );

  const read = () => {
    switch (status) {
      case "pending":
        throw suspender;
      case "error":
        throw response;
      default:
        return response;
    }
  };

  return { read };
}

export default wrapPromise;

비동기 네트워크 요청을 suspense가 알아서 인지하고 처리하면 좋겠지만, 그렇게 까지는 안되고요청 중인지 완료되었는지를 suspense가 알아차릴 수 있도록 따로 처리를 해줘야한다.

fetchImages.js

import wrapPromise from "./wrapPromise";

function fetchImages(url) {
  const promise = fetch(url).then((res) => res.json());

  return wrapPromise(promise);
}

export default fetchImages;

ImageList.jsx

import fetchImages from "./fetchImages";
const resource = fetchImages(
  "https://api.thecatapi.com/v1/images/search?limit=10"
);

const ImageList = () => {
  const images = resource.read();
  return (
    <div className="wrapper">
      {images.map((data) => {
        return <img key={data.id} src={data.url} />;
      })}
    </div>
  );
};

export default ImageList;

App.js

import { Suspense } from "react";
import ImageList from "./ImageList";
import "./styles.css";

export default function App() {
  return (
    <>
      <Suspense fallback={<Loading />}>
        <ImageList />
      </Suspense>
    </>
  );
}

const Loading = () => {
  return <div>Waiting for Loading Images...</div>;
};

사실 위에서 언급했던 것 처럼 suspense가 알아서 비동기 처리의 완료 여부를 인지하면 조금 더 편리할 것 같지만 promise를 리턴해주도록 추가적인 로직이 필요해서 suspense를 단독으로만 사용하기엔 번거롭게 느껴지긴 하는 것 같다.

그래도 Promise 로직을 구현해놓고 비슷한 컴포넌트들에서 공통적으로 사용 할 수 있다면 편할지도..?

profile
Frontend Developer

0개의 댓글