회사에서는 최신 버전의 React를 사용할 일이 없어서 잘 몰랐는데 R3F 강의를 듣다가
React 18 버전부터 제공하는 Suspense를 이용해서..~
라는 강사님의 한마디에 찾아보게 되었다.
트리 하단에 있는 컴포넌트가 아직 렌더링할 준비가 되지 않은 경우 (아직 로딩중인 경우) fallback을 이용하여 Loading indicator를 나타낼 수 있도록 해준다.
React 공식문서
기존에 React에서 API 호출을 기다리는 동안에 로딩 페이지를 보여주고 싶을때 (비동식) 나는 아래와 같은 방식으로 구현했었다.
import ImageList from "./ImageList";
export default function App() {
return (
<>
<ImageList />
</>
);
}
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>
를 사용하여 다시 구현하면 아래와 같이 구현 할 수 있다.
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가 알아차릴 수 있도록 따로 처리를 해줘야한다.
import wrapPromise from "./wrapPromise";
function fetchImages(url) {
const promise = fetch(url).then((res) => res.json());
return wrapPromise(promise);
}
export default fetchImages;
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;
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 로직을 구현해놓고 비슷한 컴포넌트들에서 공통적으로 사용 할 수 있다면 편할지도..?