갈수록 진화하는 data fetching 과정을 살펴보자~
1. Client에서 data fetching (기존 리액트 방법)
"use client"; import { useEffect, useState } from "react"; export default function Movies() { const [movies, setMovies] = useState(""); const [isLoading, setIsLoading] = useState(true); const getMovies = async () => { const response = await fetch( "https://nomad-movies.nomadcoders.workers.dev/movies" ); const json = await response.text(); setMovies(json); setIsLoading(false); }; useEffect(() => { getMovies(); }, []); return <div>{isLoading ? <div>Loading ...</div> : movies}</div>; }
- React에서는 컴포넌트를 async 함수로 선언할 수 없다. 즉, useState, useEffect를 사용해서 클라이언트 컴포넌트로 만들어줘야 함
- 즉, fetch가 클라이언트에서 일어나기 때문에, api 등이 브라우저에 노출이 됨(보안 bad) - (2에서 해결)
- isLoading을 일일히 체크해줘야 함 - (3에서 해결)
2. Server에서 data fetching + 로딩처리
const getMovies = async () => { const response = await fetch( "https://nomad-movies.nomadcoders.workers.dev/movies" ); return response.text(); }; export default async function Movies() { const movies = await getMovies(); return <div>{movies}</div>; }
- 컴포넌트를 async 함수로 선언할 수 있다!
- 서버에서 fetch 하므로, 브라우저는 api 등 아무것도 모른다.
- NextJS에서는 캐싱도 자동으로 해준다! >> 같은 요청이면 fetch를 수행하지 않고, 캐싱된 데이터를 보여줌
- but, 이 서버 컴포넌트의 작업이 완료될 때까지 이 페이지로 넘어오지 못한다. (3에서 해결)
3. 로딩 화면 보여주기(loading.tsx)
데이터 패칭이 로딩중 때, loading.tsx 파일로 화면을 보여줄 수 있다./movies/loading.tsx
const loading = () => { return <div>loading Movies ...</div>; }; export default loading;
- 파일 위치는 page.tsx와 같은 level에 있어야 한다
- 로딩중일 때, layout만 우선 보여준다
4. 병렬적으로 fetching하기(Promise.all)
-before-
두개 이상의 fetch를 할 때, 단순히 fetch 코드를 연달아 쓰면, 순차적으로 진행되어 시간이 오래걸림.
import { moviesURL } from "../page"; export const getMovie = async (id: string) => { const response = await fetch(`${moviesURL}/${id}`); return response.json(); }; export const getVideos = async (id: string) => { const response = await fetch(`${moviesURL}/${id}/videos`); return response.json(); }; export default async function Movies({ params: { id }, }: { params: { id: string }; }) { const movie = await getMovie(id); const videos = await getVideos(id); return ( <h1> {movie.title} {JSON.stringify(videos)} </h1> ); }
-after-
모든 Promise 동시에 시작해보자~ ⇒
Promise.all
(in JS)const [movie, videos] = await Promise.all([getMovie(id), getVideos(id)]);
but,, 이것은 두 promise가 모두 끝나야 결과값을 얻을 수 있다.
먼저 끝난 fetching 결과를 먼저 보여주기(Suspense)
<Suspense>
in React- 비동기 작업이 완료될 때까지 대체 UI를 표시해주는 기능
fallback
: component가 await되는 동안 표시할 컴포넌트를 render해줌
- 이는 사용자 경험을 개선하고, 특히 데이터 로딩이나 지연된 컴포넌트 로드 시 유용하게 사용할 수 있
한 page에 fetch 함수가 두개 이상 있던 것을
각각 다른 컴포넌트로 분리 → 부모 컴포넌트에서 suspense로 각 컴포넌트를 감싸서 렌더링
import { moviesURL } from "../(movies)/movies/page"; export const getMovie = async (id: string) => { const response = await fetch(`${moviesURL}/${id}`); return response.json(); }; const MovieInfo = async ({ id }: { id: string }) => { const movie = await getMovie(id); return <div>{movie.title}</div>; }; export default MovieInfo;
import { moviesURL } from "../(movies)/movies/page"; export const getVideos = async (id: string) => { const response = await fetch(`${moviesURL}/${id}/videos`); return response.json(); }; const MovieVideos = async ({ id }: { id: string }) => { const videos = await getVideos(id); return <ul>{videos.map((video) => video.name)}</ul>; }; export default MovieVideos;
import { Suspense } from "react"; import MovieInfo from "../../../components/movie-info"; import MovieVideos from "../../../components/movie-videos"; export default async function Movies({ params: { id }, }: { params: { id: string }; }) { return ( <div> <h3>Movie detail page</h3> <Suspense fallback={<div>movie info Loading...</div>}> <MovieInfo id={id} /> </Suspense> <Suspense fallback={<div>movie videos Loading...</div>}> <MovieVideos id={id} /> </Suspense> </div> ); }
이렇게 하면 부분적으로 로딩, fetching이 일어난다.
이 때, 부모 단에서 loading.tsx이 있다면, 로딩이 일어나지 않는 부분(여기선 h3)도 로딩을 기다린다. 때문에 없애는 것이 좋다.
cf) error는 컴포넌트에서 일어나면 page에도 일어나는 것으로 error.tsx는 쓰임
즉,
Page 단위 로딩 ⇒ loading.tsx
서버 컴포넌트 단위 로딩 ⇒ Suspense