본문 바로가기

Frontend

스켈레톤 UI

웹 페이지나 애플리케이션을 사용하면서 API 데이터를 불러오는 동안 흰색 화면만 나타났던 경험이 있었다. 이는 사용자 경험(UX)을 저해하는 주요 원인 중 하나이다. 이런 문제를 해결하기 위해 등장한 것이 바로 스켈레톤 컴포넌트이다.

스켈레톤 컴포넌트는 데이터를 로딩하는 동안 콘텐츠의 자리 표시자 역할을 한다. 로딩 중에도 화면이 비어 보이지 않도록 하고, 사용자가 콘텐츠를 기다리는 시간을 덜 지루하게 느끼도록 도와준다. 데이터 로딩 시간 동안 사용자에게 빈 화면 대신 콘텐츠의 레이아웃 힌트를 제공하는 것이 점점 더 중요해지고 있다.

 

https://ui.toast.com/weekly-pick/ko_20201110

스켈레톤 UI는 복잡하지 않으며, 간단한 상태 관리와 스타일링으로 구현할 수 있다. 위의 링크를 참고해봐도 좋다.

 

1. Home.jsx에서 API 호출 및 상태 관리

isLoading 이라는 상태 변수를 선언하여 로딩 상태를 관리한다. 데이터를 요청하기 전에는 로딩 상태를 활성화하고, 데이터를 가져온 후에는 로딩 상태를 비활성화한다.

import { useEffect, useState } from "react";
import Tab from "../components/home/Tab";
import List from "../components/home/List";

const DAYS = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];

const Home = () => {
  const [webtoonList, setWebtoonList] = useState([]);
  const [day, setDay] = useState(() => new Date().getDay());
  const [isLoading, setIsLoading] = useState(true); // 로딩 상태 초기화

  useEffect(() => {
    const dayString = DAYS[day];
    setIsLoading(true); // 로딩 상태 활성화
    fetch(
      `https://korea-webtoon-api-cc7dda2f0d77.herokuapp.com/webtoons?provider=NAVER&page=1&perPage=30&sort=ASC&updateDay=${dayString}`
    )
      .then((response) => {
        if (!response.ok) {
          throw new Error("Network response was not ok");
        }
        return response.json();
      })
      .then((data) => {
        setWebtoonList(data.webtoons);
        setIsLoading(false); // 로딩 상태 비활성화
      })
      .catch((error) => {
        console.error("There was a problem with the fetch operation:", error);
        setIsLoading(false); // 로딩 상태 비활성화
      });
  }, [day]);

  const handleButtonClick = (e) => {
    const selectedDay = Number(e.target.value);
    setDay(selectedDay);
  };

  return (
    <div className="home-container">
      <Tab onHandleButtonClick={handleButtonClick} selectedBtn={day} />
      {/* 로딩 상태 props 전달 */}
      <List webtoonList={webtoonList} isLoading={isLoading} />
    </div>
  );
};

export default Home;

 

2. List.jsx에서 스켈레톤 컴포넌트 렌더링

isLoading 상태에 따라 데이터를 보여주거나 스켈레톤 컴포넌트를 렌더링 한다.

// List.jsx
import React from "react";
import style from "./list.module.scss";
import className from "classnames/bind";
import { Link } from "react-router-dom";

const cx = className.bind(style);

const List = ({ webtoonList, isLoading }) => {
  return (
    <div className={cx("list-container")}>
      {isLoading
        ? Array.from({ length: 30 }).map((_, index) => (
            <div key={index}>
              <div className={cx("skeleton", "thumbnail")}></div>
              <div className={cx("skeleton", "title")}></div>
              <div className={cx("skeleton", "author")}></div>
              <div className={cx("skeleton", "score")}></div>
            </div>
          ))
        : webtoonList.map((webtoon) => (
            <Link key={webtoon.id} to={`/${webtoon.id}`} state={{ webtoon }}>
              <img src={webtoon.thumbnail} alt="thumbnail" />
              <div className={cx("title-text")}>{webtoon.title}</div>
              <div className={cx("author-text")}>
                {webtoon.authors.map((author, index) => (
                  <span key={index}>
                    {author} {index !== webtoon.authors.length - 1 ? "/" : null}
                  </span>
                ))}
              </div>
              <div className={cx("score-text")}>9.91</div>
            </Link>
          ))}
    </div>
  );
};

export default List;

 

CSS를 적용해주면 결과는 아래와 같다.