"use client";
import { useState, useEffect } from "react";
const url = "https://api.nationalize.io/";
export default function Name() {
const [data, setData] = useState(null); // 데이터를 저장할 상태
const [loading, setLoading] = useState(true); // 로딩 상태 관리
useEffect(() => {
// 데이터를 비동기로 가져오는 함수
async function fetchData() {
try {
const response = await fetch(url + "?name=john");
const result = await response.json();
setData(result); // 데이터 저장
} catch (error) {
console.error("Failed to fetch nationality data:", error);
} finally {
setLoading(false); // 로딩 상태 종료
}
}
fetchData();
}, []); // 컴포넌트가 처음 렌더링될 때 한 번 실행
return (
<div>
<h1>name page.js</h1>
<p>name page.js is a page.js in the name directory.</p>
<div>
{/* 로딩 중 메시지 표시 */}
{loading && <p>Loading...</p>}
{/* 데이터 렌더링 */}
{data &&
data.country.map((country) => (
<div key={country.country_id}>
<p>{country.country_id}</p>
<p>{country.probability}</p>
</div>
))}
{/* 데이터가 없는 경우 */}
{!loading && !data && <p>No data found.</p>}
</div>
</div>
);
}
해당 서버 컴포넌트의 선언 함수, API 요청 함수에 async를 지정해주고 await으로 비동기 요청을 하면 자동으로 data안에 응답이 들어오고 해당 응답을 가지고 next는 HTML 파일을 전송해 줄 수 있다.
export const metadata = {
title: "name page.js",
};
const url = "https://api.nationalize.io/";
async function getNationality(name) {
const response = await fetch(url + "?name=" + name);
const data = await response.json();
return data;
}
export default async function Name() {
const data = await getNationality("john");
return (
<div>
<h1>name page.js</h1>
<p>name page.js is a page.js in the name directory.</p>
<div>
{data.country.map((country) => (
<div key={country.country_id}>
<p>{country.country_id}</p>
<p>{country.probability}</p>
</div>
))}
</div>
</div>
);
}
데이터를 요청되는 동안의 상태를 정의해주어야 하는데, loading.js
라는 파일이 존재하면 Next가 자동으로 비동기 작업이 진행되는 동안, loading.js
의 컴포넌트를 대신해서 클라이언트 단에 보내준다.
Next 프레임워크가 페이지를 잘게 나눠 준비된 부분부터 먼저 보여주고 있는 것이다.
export default function loading() {
return <h1>Loading...</h1>;
}
loading.js
파일은 기본적으로 React의 Suspense에 의해 로딩 상태로 사용.fetch
, async
함수)이나 동적 import로 인해 로드가 지연되면, 이를 감지하고 loading.js
를 렌더링한다.loading.js
파일이 있으면, Next.js는 해당 경로와 연관된 비동기 작업이 있을 때 이를 자동으로 사용한다.app/dashboard/loading.js
파일은 /dashboard
경로와 관련된 로딩 상태를 처리.loading.js
의 렌더링이 종료되고 실제 페이지가 화면에 렌더링한다.싱글 스레드인 JS로 여러 요청을 순차적으로 처리하게 되면, 한 요청이 끝날 때까지 다음 코드에 접근할 수가 없게 된다.
Promise.all()
을 활용한 병렬 요청Pormise.all을 활용해, 여러 개의 비동기 요청을 병렬적으로 실행시켜 각 요청에 대한 응답을 각각 받아 최종적으로 배열에 결과를 받아올 수 있다.
그러나 이 방법으로도 각 요청이 끝났을 때 각자 처리할 수 없고 모든 요청이 끝날 때까지 대기해야만 한다.
"use client";
import { useState, useEffect } from "react";
const urls = [
"https://api.nationalize.io/?name=john",
"https://api.nationalize.io/?name=alice",
"https://api.nationalize.io/?name=emma",
];
export default function MultiFetch() {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchData() {
try {
const results = await Promise.all(
urls.map(async (url) => {
const response = await fetch(url);
return response.json();
})
);
setData(results);
} catch (error) {
console.error("Error fetching data:", error);
} finally {
setLoading(false);
}
}
fetchData();
}, []);
if (loading) return <p>Loading...</p>;
return (
<div>
<h1>Nationality Data</h1>
{data.map((result, index) => (
<div key={index}>
<h3>Request {index + 1}</h3>
{result.country.map((country) => (
<p key={country.country_id}>
{country.country_id}: {country.probability}
</p>
))}
</div>
))}
</div>
);
}
Suspense
를 활용한 병렬 요청React에서 제공하는 Suspense
를 활용한다면 독립적인 컴포넌트에서의 요청에 따른 상태를 개별적으로 관리할 수 있다.
또한, 각 요청을 병렬적으로 수행하면서 요청이 완료된 컴포넌트가 있으면 완료되는 대로 렌더링하게 해준다.
"use client";
const url = "https://api.nationalize.io/?name=aaron";
export default async function FirstRequest() {
const response = await fetch(url);
const data = await response.json();
return (
<div>
<h3>First Request</h3>
{data.country.map((country) => (
<p key={country.country_id}>
{country.country_id}: {country.probability}
</p>
))}
</div>
);
}
import React, { Suspense } from "react";
import FirstRequest from "./call1";
import SecondRequest from "./call2";
import ThirdRequest from "./call3";
export default function sss() {
return (
<div>
<h1>Suspense Example</h1>
<Suspense fallback={<p>Loading First Request...</p>}>
<FirstRequest />
</Suspense>
<Suspense fallback={<p>Loading Second Request...</p>}>
<SecondRequest />
</Suspense>
<Suspense fallback={<p>Loading Third Request...</p>}>
<ThirdRequest />
</Suspense>
</div>
);
}
try-catch
로 에러 핸링export default async function Page() {
try {
const response = await fetch("https://api.example.com/data", {
next: { revalidate: 60 },
});
if (!response.ok) {
throw new Error("Failed to fetch data");
}
const data = await response.json();
return (
<div>
<h1>Data:</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
} catch (error) {
return <div>Error: {error.message}</div>;
}
}
error.js
로 에러 페이지 처리에러 페이지(error.js
)를 생성해 에러 발생 시 렌더링되도록 설정할 수 있다.
"use client";
export default function Error({ error, reset }) {
return (
<div>
<h2>Something went wrong!</h2>
<p>{error.message}</p>
<button onClick={() => reset()}>Try again</button>
</div>
);