
// Home
export const Home = () => {
  const { data: characters, status } = useFetch({
    fetchFunction: fetchCharacters,
    args: [50],
    cacheKey: ROUTE_PATH.HOME,
  });
  const charactersList = characters?.results;
  return (
    <S.Container>
      // 비동기 요청이 처리될 때까지 Loading 컴포넌트 렌더링
      {status === 'pending' ? (
        <Loading />
      ) : (
        <S.Characters>
// Detail
export const Detail = () => {
  const { id } = useParams();
  const { data: characterDetail, status } = useFetch({
    fetchFunction: fetchCharacterDetail,
    args: [id],
    cacheKey: `${ROUTE_PATH.DETAIL}/${id}`,
  });
  const detailsInfo = characterDetail?.results[0];
 return (
    <>
      // 비동기 요청이 처리될 때까지 Loading 컴포넌트 렌더링
      {status === 'pending' ? (
        <Loading />
      ) : (
        <S.Container>
React의
Suspense와lazy기능을 사용하여 이러한 문제를 효과적으로 해결할 수 있다.
비동기 작업의 결과를 기다리는 경계를 설정하는 컴포넌트다.비동기 경계 설정
: Suspense 내부의 컴포넌트들이 데이터를 불러오는 동안 대체 컴포넌트를 보여준다.
이를 통해 비동기 작업 중에도 사용자에게 즉시 반응하는 UI를 제공할 수 있다.
통합된 로딩 상태 관리
: 전통적으로 각 컴포넌트 내에서 로딩 상태를 관리해야 했으나,
Suspense를 사용하면 애플리케이션 전체에서 중앙 집중식으로 로딩 상태를 관리할 수 있다.
코드 분할과 연계
: React의 lazy 함수와 결합하여 컴포넌트의 코드 분할을 쉽게 할 수 있다.
이렇게 하면 특정 컴포넌트가 실제로 렌더링 될 때까지 해당 컴포넌트의 코드를 로딩하지 않아, 초기 로딩 성능이 향상된다.
import { Suspense } from 'react';
import Albums from './Albums.js';
export default function ArtistPage({ artist }) {
  return (
    <>
      <h1>{artist.name}</h1>
      <Suspense fallback={<Loading />}>
        <Albums artistId={artist.id} />
      </Suspense>
    </>
  );
}
function Loading() {
  return <h2>🌀 Loading...</h2>;
}
Promise를 throw 해야한다.pending를 throw한다.pending이면 fallback에 지정된 컴포넌트를 렌더링 한다.fulfilled되면) 해당 컴포넌트(Albums)를 렌더링 한다.import { Suspense, lazy } from 'react';
const OtherComponent = lazy(() => import('./OtherComponent'));
function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <OtherComponent />
      </Suspense>
    </div>
  );
}
import { useState, useEffect, useRef } from 'react';
import { useCacheContext } from '@/contexts/CacheContext';
type Status = 'initial' | 'pending' | 'fulfilled' | 'rejected';
interface UseFetch<T> {
  data?: T;
  status: Status;
  error?: Error;
  cacheKey: string;
}
interface FetchOptions<T> {
  fetchFunction: (...args: any[]) => Promise<T>;
  args: any[];
  cacheKey: string;
}
export const useFetch = <T>({
  fetchFunction,
  args,
  cacheKey,
}: FetchOptions<T>): UseFetch<T> => {
  const [state, setState] = useState<UseFetch<T>>({
    status: 'initial',
    data: undefined,
    error: undefined,
    cacheKey,
  });
  const { cacheData, isDataValid } = useCacheContext();
  const activePromise = useRef<Promise<void> | null>(null);
  useEffect(() => {
    let ignore = false;
    const fetchData = async () => {
      if (ignore) return;
      try {
        const response = await fetchFunction(...args);
        cacheData(cacheKey, response);
        setState((prevState) => ({
          ...prevState,
          status: 'fulfilled',
          data: response,
          cacheKey,
        }));
      } catch (error) {
        setState((prevState) => ({
          ...prevState,
          status: 'rejected',
          error: error as Error,
          cacheKey,
        }));
      }
    };
    if (state.status === 'initial') {
      if (isDataValid(cacheKey)) {
        setState((prevState) => ({
          ...prevState,
          status: 'fulfilled',
          data: cacheData(cacheKey),
          cacheKey,
        }));
      } else {
        setState((prevState) => ({
          ...prevState,
          status: 'pending',
        }));
        activePromise.current = fetchData();
      }
    }
    return () => {
      ignore = true;
    };
  }, [fetchFunction, cacheKey, cacheData, isDataValid, state.status]);
  if (state.status === 'pending' && activePromise.current) {
    throw activePromise.current;
  }
  if (state.status === 'rejected' && state.error) {
    throw state.error;
  }
  return state;
};
throw할 Promise를 useRef에 저장한다.
pending이고 activePromise.current에 값이 있으면 해당 Promise를 던져 Suspense를 활성화시킨다.rejected이고 오류가 있으면 해당 오류를 던져서 Error Boundary에서 처리할 수 있게 한다.// Home
export const Home = () => {
  const { data: characters } = useFetch({
    fetchFunction: fetchCharacters,
    args: [50],
    cacheKey: ROUTE_PATH.HOME,
  });
  const charactersList = characters?.results;
  return (
    <S.Container>
        <S.Characters>
        // 렌더링 코드 중략
// Detail
export const Detail = () => {
  const { id } = useParams();
  const { data: characterDetail } = useFetch({
    fetchFunction: fetchCharacterDetail,
    args: [id],
    cacheKey: `${ROUTE_PATH.DETAIL}/${id}`,
  });
  const detailsInfo = characterDetail?.results[0];
 return (
    <>
      {detailsInfo && (
        <S.Container>
        // 렌더링 코드 중략
import { Suspense, lazy } from 'react';
import { createBrowserRouter } from 'react-router-dom';
import { Layout } from '@/routes/Layout';
import { ROUTE_PATH } from '@/router/routePath';
import { NotFound } from '@/routes/NotFound';
import { ErrorBoundary } from '@/components/ErrorBoundary';
import { Loading } from '@/components/Loading';
import Home from '@/routes/Home';
const Detail = lazy(() => import('@/routes/Detail'));
export const router = createBrowserRouter([
  {
    element: <Layout />,
    path: ROUTE_PATH.ROOT,
    errorElement: <NotFound />,
    children: [
      {
        path: ROUTE_PATH.HOME,
        element: (
          <ErrorBoundary>
            <Suspense fallback={<Loading />}>
              <Home />
            </Suspense>
          </ErrorBoundary>
        ),
      },
      {
        path: ROUTE_PATH.DETAIL,
        element: (
          <ErrorBoundary>
            <Suspense fallback={<Loading />}>
              <Detail />
            </Suspense>
          </ErrorBoundary>
        ),
      },
    ],
  },
]);