원래 리액트에서는 useState, useEffect 등 훅을 사용해서 페이지를 만들고, api를 가져와 데이터 패칭할때도 꽤나 복잡했다.
하지만 Next.js 프레임워크는 다르다. 아주 간단하게 코드 만들기 가능!
import React, { useState, useEffect } from 'react';
const URL = "url";
function HomePage() {
const [movies, setMovies] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchMovies = async () => {
try {
// 비동기 호출을 통해 데이터를 패치
await new Promise((resolve) => setTimeout(resolve, 10000));
const response = await fetch(URL);
const json = await response.json();
setMovies(json);
} catch (error) {
console.error("Failed to fetch movies:", error);
} finally {
setLoading(false);
}
};
fetchMovies();
}, []);
if (loading) return <div>Loading...</div>;
return (
<div>
{movies ? JSON.stringify(movies) : "No movies available"}
</div>
);
}
export default HomePage;
//Next.js
export const metadata = {
title: "Home",
};
const URL = "url";
// 비동기적으로 영화 데이터를 가져오는 함수
async function getMovies() {
await new Promise((resolve) => setTimeout(resolve, 10000));
const response = await fetch(URL);
const json = await response.json();
return json;
}
// Next.js의 기본 비동기 함수 컴포넌트
export default async function HomePage() {
const movies = await getMovies();
return <div>{JSON.stringify(movies)}</div>; // 데이터를 JSON 문자열로 변환해 렌더링
}
homePage component가 async여야 하는 이유는 Next.js가 이 component에서 await해야 하기 때문이다.
await이 끝나면 마지막 html 부분을 전달해주기 때문에 그 전까지는 브라우저가 백엔드 작업이 덜 끝났다고 생각한다.
그래서 그 사이에 loading.tsx/jsx을 넣어주어 화면에 뿌려준다.
<Loading />
const html = await HomePage()
isLoading? <Loading /> : html
-> 이와 같은 작업을 Next.js 프레임 워크에서는 자동으로 처리해준다.(async, await, loading.jsx로)
먼저, layout에 있는 공통 요소를 뿌려주고, 그 다음에 데이터 패칭된 부분을 보여주는데 보여주기전 loading.jsx 파일이 있다면 자동으로 로딩 페이지를 뿌려주기 때문에 위에는 layout안에 있는 공통 헤더가 나오고 있고, 그 아래에 Loading... 텍스트가 보여지는 것이다.
import { API_URL } from "../../../(home)/page";
async function getMovie(id: string) {
console.log(`Fetching movies: ${Date.now()}`);
await new Promise((resolve) => setTimeout(resolve, 5000));
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, 5000));
const response = await fetch(`${API_URL}/${id}/videos`);
return response.json();
}
export default async function MovieDetail({
params: { id },
}: {
params: { id: string };
}) {
console.log("===========");
console.log("start fetching");
const movie = await getMovie(id); //순차적으로 진행되기에 너무 느림
const video = await getVideos(id); //순차적으로 진행되기에 너무 느림
console.log("end fetching");
return <h1>{movie.title}</h1>;
}
const [movie, videos] = await Promise.all([getMovie(id), getVideos(id)])
import { API_URL } from "../../../(home)/page";
async function getMovie(id: string) {
console.log(`Fetching movies: ${Date.now()}`);
await new Promise((resolve) => setTimeout(resolve, 5000));
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, 5000));
const response = await fetch(`${API_URL}/${id}/videos`);
return response.json();
}
export default async function MovieDetail({
params: { id },
}: {
params: { id: string };
}) {
console.log("===========");
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, videos] = await Promise.all([getMovie(id), getVideos(id)]); //한번에 진행
return <h1>{movie.title}</h1>;
지금 위 소스는 Promise.all부분이 다 끝나야지만 리턴값이 화면에 뿌려진다.
지금은 뿌려지는 데이터가 2개뿐이라 모르겠지만 많은 데이터를 fetch하게 되면 화면에 뿌려지는 것이 없으니... 좋지 못한 코드인 것이다.
그래서 다 끝날때까지 기다리는 것이 아니라 다 분리를 시켜서 한번에 끝나도록 해야함.
그것이 suspense임.
1. 파일 생성(movie-videos.tsx, movie-info.tsx)
2. 데이터 패칭해주는 소스 코드 복붙해서 넣어줌
// components/movie-videos.tsx
async function getVideos(id: string) {
console.log(`Fetching videos: ${Date.now()}`);
await new Promise((resolve) => setTimeout(resolve, 5000));
const response = await fetch(`${API_URL}/${id}/videos`);//import 시켜주기
return response.json();
}
export default async function Movievideos({id}: {id:string}) {
const videos = await getVideos(id);
return <h6>{JSON.stringify(videos)}</h6>
}
// components/movie-info.tsx
async function getMovie(id: string) {
console.log(`Fetching movies: ${Date.now()}`);
await new Promise((resolve) => setTimeout(resolve, 5000));
const response = await fetch(`${API_URL}/${id}`);//import
return response.json();
}
export default async function MovieInfo({id}: {id:string}) {
const movie = await getMovie(id);
return <h6>{JSON.stringify(movie)}</h6>
}
export default async function MovieDetail({
params: { id },
}: {
params: { id: string };
}) {
//각각 다른 파일로 분리시켰기 때문에 주석 혹은 지우기
//const [movie, videos] = await Promise.all([getMovie(id), getVideos(id)]);
return <h1>{movie.title}</h1>;
}
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>
)
Page 단위 로딩 => loading.tsx
서버 컴포넌트 단위 로딩 => Suspense
"use client";//필수로 선언해줘야 함
export default function ErrorOMG() {
return <h1>lol something broke....</h1>;
}
프레임워크라 확실히 간단하고 좋은 방법이 많아서 사용하기에 편해보이지만 외워야할것(?)이 많아서 더 헷갈리는 부분이 많다.
아직 데이터 패칭부분이 어렵지만 지금까지 배웠던 개념중에는 nextjs가 가장 간단하고 효과적인 코드인건 확실해보인다.
예제를 더 풀어보면서 열심히 손에 익혀야겠다.