[NextJS] Data Fetching

Jimin·2024년 4월 11일
0

Next.JS

목록 보기
3/13

CRS에서 useState를 사용하면 개발자 도구에서 요청 url이 그대로 노출되기 때문에 보안적 문제 때문에 데이터베이스는 client가 아닌 server쪽에서 접근해야했다. (중간 매개체가 필요했다.)
↔️ 하지만 최신 버전의 NextJS와 server component가 있다면, API가 필요 없다. (중간 매개체가 필요 없다.)

Server Side

Server component에서 fetch를 요청할 때는 Client Component에서 fetch를 요청할 때보다 단순하다. (코드가 줄어든다.)

CSR

async function getMovies() {
  const reponse = await fetch(URL);
  const json = await reponse.json();
  return json;
}

SSR - Server Component 사용

async function getMovies() {
  return fetch(URL).then(response => response.json());
}
  • seStateuseEffect 사용이 사라졌다.
  • fetch된 URL을 자동으로 캐싱한다.
  • 개발자 도구에서 API 요청이 더 이상 보이지 않는다.

async 를 사용하는 이유? ➡️ await 를 사용하기 위해서
await 는? ➡️ 어떤 일이 발생하기를 기다리려고 await 를 사용한다.

Async&Await 사용 예시 코드

HomePage 함수 앞에 async 가 붙고, movies라는 변수에 await 가 사용되었다.

async function getMovies() {
  return fetch(URL).then(response => response.json());
}

export default async function HomePage() {
  const movies = await getMovies();
  return (
  <div>
    {JSON.stringify(movies)}
  </div>
  );
}

상단에 로딩 바는 백엔드에서 로딩이 발생하고 있다는 의미이다.
➡️ 사용자 입장에서는 백엔드에서 로딩이 발생하고 있더라도 UI적으로 반응이 바로 와야한다.

async function getMovies() {
  await new Promise((resolve) => setTimeout(resolve,5000)); // 억지로 5초 로딩
  console.log("I'm fetching!");
  return fetch(URL).then(response => response.json());
}

Loading Components

loading.tsx 파일을 만들면, 백엔드 측에서 로딩을 해야할 때 임시로 사용자에게 해당 페이지UI를 제공한다.
이름은 반드시 loading 이어야 한다!
해당 폴더 내에서만 작동한다.

export default function Loading() {
    return <h2>Loading...</h2>;
}

Parallel Requests - Promise.all

fetch해야하는 요청이 두 가지인데, 각각 따로 await를 요청하면 순차처리(직렬요청)이 되기 때문에 시간이 오래걸린다.
이를 해결하기 위해 Promise.all 을 사용한다.

import { API_URL } from "@/app/(home)/page";

async function getMovie(id:string) {
    console.log(`Fetching movies: ${Date.now()}`)
    await new Promise((resolve) => setTimeout(resolve, 2000));
    const response = await fetch(`${API_URL}/${id}`);
    return response.json();
}

async function getVideos(id:string) {
    console.log(`Fetching videos: ${Date.now()}`)
    await new Promise((resolve) => setTimeout(resolve, 2000));
    const response = await fetch(`${API_URL}/${id}/videos`);
    return response.json();
}

export default async function MovieDetail({
    params: {id} // [id] 에서 id를 가져온다.
}: {params:{id:string};
}) {
    console.log('start fetching');
    const [movie, videos] = await Promise.all([getMovie(id), getVideos(id)]);
    console.log('end fetching');
    return <h1>{movie.title}</h1>;
}
const movie = await getMovie(id);
const videos = await getVideos(id);
⬇️
const [movie, videos] = await Promise.all([getMovie(id), getVideos(id)]);

Suspense - fallback

movie와 videos를 동시 요청하지만, 먼저 데이터가 받아지는 쪽을 먼저 UI에 반영하도록 할 때 사용한다.
➡️ 동시 요청할 부분을 두 개의 component로 나눈다.

이 각 component는 자신에 관한 데이터만 fetch했고,
Suspense component 가 데이터를 fetch하기 위해 이 안의 component들을 await 한다.

suspense component 에는 fallback 이라는 prop 이 있고,
fallback prop은 component가 await되는 동안 표시할 메시지를 render할 수 있게 해준다.

page.tsx에서 data fetching을 요청하지 않기 때문에 loading.tsx은 아무것도 하지 않는다.

// ====================page.tsx==================== \\

export default async function MovieDetail({
    params: {id} // [id] 에서 id를 가져온다.
}: {params:{id:string};
}) {
    return (
    <div>
        <Suspense fallback={<h1>Loading movie info</h1>} >
            <MovieInfo id={id} />
        </Suspense>
        <Suspense fallback={<h1>Loading movie videos</h1>}>
            <MovieVideos id={id} />
        </Suspense>
    </div>
    );
}
// ====================movie-videos.tsx==================== \\
import { API_URL } from "@/app/(home)/page";
async function getVideos(id:string) {
    console.log(`Fetching videos: ${Date.now()}`)
    await new Promise((resolve) => setTimeout(resolve, 3000));
    const response = await fetch(`${API_URL}/${id}/videos`);
    return response.json();
}

export default async function MovieVideos({id}:{id:string}) {
    const videos = await getVideos(id);
    return <h6>{JSON.stringify(videos)}</h6>
}

정리

  • Server Component에서 fetch 요청을 하게 되면 더 이상 useState나 useEffect는 사용하지 않아도 된다.
  • fetch 하기 위해서는 해당 function은 async 가 되어야 한다.
  • async 후 await 를 이용해서 fetch 한다.
  • 각각의 fetch 요청들은 순차적으로 일어난다.
    ➡️ 그렇기 때문에 Promise.all 을 사용해서 병렬 요청 한다.
    • 두 컴포넌트가 로딩이 될 때 까지 각각의 로딩 화면을 개별적으로 보여주기 위해,
      Suspense Component 를 사용하여 로딩상태를 분리한다.
    • page.tsx에서 fetch를 하면 전체 페이지가 로딩 페이지가 되지만,
      Suspense를 이용하면 각각의 component UI들을 await 할 수 있게 된다.

➡️ 해야할 건 단순하다. Component를 async 하고 해당 Component를 Suspense로 감싸준다!

Error handling - error.tsx

error.tsx 만 만들어 놓으면 해당 페이지 혹은 자식 페이지에 에러가 있을 때 자동으로 해당 error.tsx UI가 반영된다.

에러난 부분만 반영되고 나머지는 정상적으로 보인다.
단, error Component에는 반드시 use client 를 사용해야 한다.

"use client";
export default function ErrorMOG() {
    return <h1>lol something is broken,,,!</h1>
}
profile
https://github.com/Dingadung

0개의 댓글