React - Suspense

응애 나 프론트애긔👶·2023년 5월 16일
0
post-thumbnail
post-custom-banner

데이터가 늦게 들어온다?


Promise를 사용한 비동기 작업이나 서버에서 데이터를 Fetching 할 때
데이터가 화면에 뿌려지기 전 미리 만들어진 컴포넌트들이 렌더링이 되고
그 이후 뒤늦게 들어온 데이터들이 화면상에 보여지게 된다.

뒤늦게 데이터들이 들어오게 된다면 사용자들은 머리에 물음표가 찍히게 될 것이며
완성도가 낮은 웹 혹은 앱이라고 느껴질 수가 있다.

그래서 웹에서 다른 페이지를 넘어가거나 데이터를 불러올 때 이러한 화면을 본 적이 있을 것이다.

Loader, Circular 등 다양한 이름으로 불린다.

우린 이러한 로딩 컴포넌트들을 통해 사용자에게 기다림을 요구하여
뒤늦게 렌더링 되는 우스꽝스러운 페이지를 안보여줘도 된다.

생각보다 쉬운 로직


import "./styles.css";
import React, { useEffect } from "react";

export default function App() {
  const [loading, setLoading] = React.useState(true);
  const [data, setData] = React.useState("하이");

  React.useEffect(() => {
    const promise = new Promise((resolve) => {
      setTimeout(() => {
        resolve("짜잔!");
      }, 3000);
    });
    promise.then((res) => {
      setData(res);
      setLoading(false);
    });
  }, []);

  if (loading) return <>데이터를 받아오는 중입니다...</>;

  return <div className="App">{data}</div>;
}

https://codesandbox.io/s/new-morning-jnq3xp?file=/src/App.js

간단한 예시 코드를 보자면 loading이라는 boolean형태의 State를 만들고
3초 후 짜잔이라는 값을 받은 후 로딩의 불린 값을 변경해준다.

이후 if문을 사용하여 리턴하는 컴포넌트를 로딩 컴포넌트와 원래 컴포넌트를
loading 불린 값에 따라 렌더링 해주면 된다.

그럼 왜 Suspense를 써야하나? 🤔


먼저 Suspense가 무엇인지부터 알아보자.

React Suspense는 리액트 16.8버전 이후에 생겨난 선언적으로 사용하는 기능이다.
이 기능은 React.lazy와 함께 렌더링 준비가 안된 컴포넌트들을 기다리기 위해
로딩 페이지를 보여주는 역할로 사용된다.

그렇다면 위에서 State를 사용과 무엇이 다른가?

첫번째로 렌더링 전에 네트워크 콜을 시작하기 때문이라고 하는데 성능적인 부분에서 Suspense가 우위에 서있다는 블로그를 보았다.
블로그 주소: https://developer-alle.tistory.com/428

두번째로는 각 컴포넌트마다 State를 만들어둘 필요가 없다.
데이터를 기다려야하는 컴포넌트들이 여러개가 있다면 각 컴포넌트마다 State를 만들어 사용해야하고 이를 관리해야한다.
하지만 Suspense는 Provider와 비슷한 형태로 컴포넌트 혹은 라우터를 감싸 사용하여 따로 State를 만들 필요가 없다.

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

// 서버데이터 가져오기
const fetchingData = () => {
  let serverData = [];
  const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
      serverData = ["Ed Sheeran", "Lizzo", "Charlie Puth"];
      resolve(serverData);
    }, 2000);
  });
  return promise;
};

// suspense 함수
const getArtist = (promise) => {
  let ready = false;
  let response = [];
  promise.then((res) => {
    ready = true;
    response = res;
  });
  return {
    read() {
      if (!ready) {
        throw promise;
      }
      if (ready) {
        return response;
      }
    }
  };
};

// 가져온 서버데이터를 렌더링
const ServerDataRender = ({ getArtist }) => {
  console.log(getArtist.read());
  const artist = getArtist.read();
  return (
    <>
      {artist.map((singer) => (
        <div key={singer}>{singer}</div>
      ))}
    </>
  );
};

export default function App() {
  return (
    <>
      <Suspense fallback={<p>잠시만 기다려주세요.</p>}>
        <ServerDataRender getArtist={getArtist(fetchingData())} />
      </Suspense>
    </>
  );
}

https://codesandbox.io/s/unruffled-taussig-kpiw6p?file=/src/App.js:0-1078

Suspense는 기본적으로 promise를 throw로 반환하게 된다.
반환된 throw로 promise가 반환되게 된다면 Suspense의 fallback 컴포넌트가 렌더링 되게 된다.

만약 원하는 로딩페이지가 있다면 fallback 속성 안에 넣어주면 된다.

데이터가 정상적으로 들어왔다면 해당 데이터를 직접적으로 바로 사용할 수 있다.

위의 예시코드를 보자면 2초 뒤에 가수들의 이름이 나열되어 있는 배열을 가져오게 되어있다.
2초 동안은 ready의 불린 값이 false이기 때문에 throw로 promise를 반환하여 fallback에 있는
"잠시만 기다려주세요." 라는 문구가 반환되지만 이후 response 데이터가 정상적으로 들어오고 배열을 반환하게 된다.

느낀점


물론 방식마다 다르겠지만 loading이라는 State를 활용한 로딩페이지 구현과 Suspense의 성능차이는 없을거라 생각을 했지만 나름 약간의 성능차이가 있다는 것에 조금 놀랐다.

Suspense와 Lazy를 사용하여 로딩 페이지를 보다 쉽게 관리할 수 있다는 점이 큰 강점인거 같다.
특히 Route에서 사용이 가능한 것이 정말 좋은 강점이다.

다른 블로그에서 CSR뿐만 아니라 SSR에서도 큰 활약을 한다고하니 나중에 Next.js를 사용할 때도
이 기능을 사용해보고 싶다는 생각이 들었다.
또 React-Query / SWR 과 같은 서버 상태관리 툴들과도 잘 어울린다고하니 필요성을 확실히 느끼고 간다.

post-custom-banner

0개의 댓글