Q1. 기존 service에서 domainService 메서드를 추가한 이유는 해당 key값인 api가 없다면 별도의 error 문구를 던지기 위해서 작성했는데 이렇게 하지 않으면 어떤식으로 controller에서 error를 던질 수 있어?
A1. controller 단에서 개별 함수(훅)으로 만들어서 각각 export하면 내보낼 당시 없는 메서드의 경우 에러가 발생하게 되니 해결되며, react/nextjs 생태계의 표준 모범에 부합.
Q2. 서버 컴포넌트용 API라는 건 별도의 유저 조작없이 특정 페이지 접속시 보이는 데이터로 예를 들자면 쇼핑몰 페이지에서 추천 상품 목록이 아닌 일반적인 것으로 의류 중 상의 탭을 클릭하면 누구에게나 공통으로 보일 데이터의 경우엔 서버 컴포넌트용 APi를 제작해서 fetch하여 페이지 로드 시 서버에서 데이터를 가져와서 바로 보일 수 있게 하면 좋겠다는거야?
A2. ㅇㅇ 맞음, 누구에게나 공통으로 보일 데이터인 "페이지의 핵심이 되는 초기 콘텐츠"는 서버 컴포넌트에서 fetch로 처리, 초기 콘텐츠 위에서 일어나는 사용자 동적인 상호작용은 클라이언트 컴포넌트와 TanStack Query가 담당.
Q2-1. fetch와 TanStack Query 모두 사용하는 즉, 서버 컴포넌트와 클라이언트 컴포넌트 둘다 처리가 필요하다면 같은 api 작성을 2번해야하는데 이는 어떤식으로 관리해야 효율적일까?
A2-1. 실제 API 요청 로직은 한 번만 작성하고, 해당 로직을 서버 컴포넌트와 TanStack Query에서 가져다 사용하기.
Step 1: 순수 API 함수 계층 만들기
services/domains.ts (새로운 단일 API 계층)
// 이 파일은 React와 무관한 순수 TypeScript/JavaScript 파일입니다.
// 1. 파라미터를 받아 URL을 만들고, fetch를 호출하고, 결과를 반환하는 핵심 로직
async function fetchDomainList(params) {
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080';
const queryString = new URLSearchParams(params).toString();
const url = `${API_BASE_URL}/api/domain/get/list?${queryString}`;
// Next.js의 확장된 fetch를 사용합니다.
const response = await fetch(url, {
// 서버 컴포넌트에서 이 함수를 호출할 때 캐시 전략을 정할 수 있습니다.
// 클라이언트에서는 이 옵션이 무시됩니다.
cache: 'no-store',
});
if (!response.ok) {
// 에러 처리는 여기서 일관되게 할 수 있습니다.
throw new Error('도메인 목록을 가져오는데 실패했습니다.');
}
return response.json(); // Promise<Data>를 반환합니다.
}
// 필요하다면 다른 API 함수들도 이런 식으로 만듭니다.
async function fetchDomainDetail(id) {
// ...
}
// 2. 작성한 함수들을 export 합니다.
export const domainAPI = {
getList: fetchDomainList,
getDetail: fetchDomainDetail,
};
Step 2: 서버 컴포넌트에서 이 함수를 직접 사용하기
app/domains/page.tsx (서버 컴포넌트)
import { domainAPI } from '@/services/domains'; // Step 1에서 만든 함수를 import
export default async function DomainsPage() {
// 서버에서 페이지를 렌더링하기 전에 데이터를 직접 호출하고 기다립니다.
const initialDomains = await domainAPI.getList({ page: 1, limit: 10 });
return (
<main>
<h1>도메인 목록 (초기 데이터는 서버 렌더링)</h1>
{/*
이 초기 데이터를 클라이언트 컴포넌트에 넘겨주어
클라이언트에서의 추가적인 상호작용을 처리하게 합니다.
*/}
<DomainListClient initialData={initialDomains} />
</main>
);
}
Step 3: TanStack Query 훅(Controller)에서 이 함수를 재사용하기
controllers/domainController.ts (기존 Controller 수정)
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { domainAPI } from '@/services/domains'; // Step 1에서 만든 함수를 import
export const useFetchDomain = (params) => {
return useQuery({
queryKey: ['domainList', params],
// ✅ 핵심: 실제 API 호출 로직을 재사용합니다.
queryFn: () => domainAPI.getList(params),
enabled: !!params,
staleTime: 1000 * 60 * 5, // 5분
});
};
// Mutation을 위한 래퍼 훅도 동일한 원리로 만들 수 있습니다.
// export const useUpdateDomain = ...```
이제 `useFetchDomain` 훅은 TanStack Query의 캐싱, 상태관리 등의 기능만 담당하는 **"래퍼(Wrapper)"**가 되고, 핵심 API 로직은 `services/domains.ts`에 위임됩니다.
---
#### Step 4: 클라이언트 컴포넌트에서 완성하기 (하이브리드 패턴)
서버 컴포넌트로부터 초기 데이터를 받고, 이후의 상호작용은 TanStack Query 훅을 사용하는 클라이언트 컴포넌트를 만듭니다.
**`components/DomainListClient.tsx` (클라이언트 컴포넌트)**
```tsx
'use client';
import { useState } from 'react';
import { useFetchDomain } from '@/controllers/domainController';
export default function DomainListClient({ initialData }) {
const [page, setPage] = useState(1);
const [filters, setFilters] = useState({});
// TanStack Query 훅을 사용합니다.
const { data, isLoading } = useFetchDomain(
// 현재 페이지와 필터 상태를 파라미터로 전달
{ page, ...filters },
{
// ✅ 최적화: 서버에서 받아온 초기 데이터가 있으므로, 첫 렌더링 시에는 API를 다시 호출하지 않습니다.
initialData: initialData,
}
);
// ... 필터링, 페이지네이션 등 UI 로직 ...
const handleFilterChange = () => { /* setFilters(...) */ };
const handleNextPage = () => setPage(p => p + 1);
return (
<div>
{/* 필터 UI */}
{/* ... */}
{isLoading && <p>데이터 갱신 중...</p>}
<ul>
{data?.items.map((domain) => (
<li key={domain.id}>{domain.name}</li>
))}
</ul>
<button onClick={handleNextPage}>다음 페이지</button>
</div>
);
}'노트' 카테고리의 다른 글
| [ 노트 ] 금융 & 경제 & 경영 - 기초 상식 (8) | 2024.10.18 |
|---|---|
| [노트] 동기 부여 글 [에일리] (0) | 2024.07.12 |
| [노트] JPG, PNG, SVG & HTML 동작 순서 2024.06.04(화) (0) | 2024.06.04 |
| [노트] 자바스크립트 Set, localeCompare, 객체지향 프로그래밍 (2024.05.25) (0) | 2024.05.25 |