1. 오늘 학습 주제
React에서 반복되는 데이터 요청 로직을 custom hook으로 분리하는 방법과, Next.js의 App Router를 이용해 페이지 이동 구조를 만드는 방법을 학습했다.
단순히 화면을 만드는 것을 넘어서, 로직을 재사용 가능하게 분리하고, URL을 기준으로 페이지를 구성하는 방식까지 연결해서 이해할 수 있었다.
- custom hook으로 데이터 요청 로직 재사용하기
- AbortController로 fetch 요청 취소하기
- Next.js App Router의 기본 페이지 구조 이해하기
- Link로 페이지 이동하기
- 공통 레이아웃과 중첩 레이아웃 구성하기
- usePathname, useRouter, useParams, useSearchParams 사용하기
- 동적 라우팅과 쿼리 스트링 처리하기
2. Custom Hook이란
custom hook은 여러 컴포넌트에서 반복되는 Hook 로직을 함수로 분리한 것이다.
이름은 반드시 use로 시작해야 하며, 내부에서 useState, useEffect 같은 Hook을 사용할 수 있다.
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch(url)
.then(response => response.json())
.then(responseData => setData(responseData));
}, [url]);
return { data, loading, error };
}
- custom hook은 반복되는 상태 관리와 effect 로직을 재사용하기 위해 만든다.
- 일반 컴포넌트가 아니라 Hook 로직을 담는 함수이다.
- 반환값을 통해 컴포넌트가 필요한 상태를 받아 사용할 수 있다.
3. useFetch custom hook
오늘 예제에서는 API 요청 로직을 useFetch(url)이라는 custom hook으로 분리했다.
const { data: users, loading, error } = useFetch('https://jsonplaceholder.typicode.com/users');
이 방식의 장점은 컴포넌트가 직접 fetch 로직을 길게 작성하지 않아도 된다는 점이다.
컴포넌트는 오직 결과 상태에 따라 화면을 어떻게 보여줄지만 생각하면 된다.
- API 요청 로직을 컴포넌트 밖으로 분리할 수 있다.
- 데이터, 로딩, 에러 상태를 한 번에 재사용할 수 있다.
- 화면 렌더링과 데이터 요청 책임을 분리할 수 있다.
4. AbortController로 요청 취소하기
custom hook 안에서는 AbortController를 사용해 통신 취소까지 처리했다.
const controller = new AbortController();
fetch(url, { signal: controller.signal })
컴포넌트가 사라질 때 cleanup 함수에서 요청을 취소한다.
return () => controller.abort();
- 컴포넌트가 언마운트되었는데도 요청이 계속 진행되면 불필요한 작업이 생길 수 있다.
- AbortController를 사용하면 fetch 요청을 안전하게 취소할 수 있다.
- cleanup 함수와 함께 쓰면 메모리 누수와 불필요한 상태 업데이트를 방지할 수 있다.
5. 로딩, 에러, 데이터 상태 분리
custom hook은 단순히 데이터만 반환하지 않고, loading, error도 함께 반환했다.
if (loading) {
return <p>로딩 중...</p>;
}
if (error) {
return <p>오류가 발생했습니다: {error.message}</p>;
}
if (!users) {
return <h3>데이터가 없습니다.</h3>;
}
정리
- 비동기 데이터는 항상 성공만 하는 것이 아니다.
- 로딩, 에러, 빈 데이터 상태를 나누어 생각해야 한다.
- custom hook과 Early Return을 함께 쓰면 컴포넌트 코드가 깔끔해진다.
6. Next.js App Router 기본 구조
Next.js App Router에서는 src/app 폴더를 기준으로 라우팅이 만들어진다.
폴더 구조가 URL 구조와 거의 그대로 연결된다.
예를 들어 아래와 같은 구조가 있을 때
- src/app/page.js
- src/app/about/page.js
- src/app/menu/page.js
각각 다음 경로에 대응된다.
- /
- /about
- /menu
- page.js는 실제 페이지를 의미한다.
- 폴더명이 URL 경로가 된다.
- App Router는 파일 시스템 기반 라우팅 방식이다.
7. Link를 이용한 페이지 이동
Next.js에서는 일반 <a> 태그 대신 Link 컴포넌트를 사용해 페이지 이동을 한다.
import Link from "next/link";
<Link href="/">HOME</Link>
<Link href="/about">소개</Link>
<Link href="/menu">메뉴</Link>
정리
- Link는 Next.js의 클라이언트 측 라우팅을 지원한다.
- 전체 새로고침 없이 페이지를 이동할 수 있다.
- 네비게이션 메뉴를 만들 때 기본적으로 사용한다.
8. Root Layout
layout.js는 여러 페이지에 공통으로 적용되는 레이아웃을 정의하는 파일이다.
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
이후 공통 컴포넌트를 감싸서 모든 페이지에 공통 UI를 줄 수 있다.
<body>
<Layout>
{children}
</Layout>
</body>
정리
- layout.js는 공통 레이아웃을 담당한다.
- children 자리에 현재 페이지 내용이 들어온다.
- 헤더, 내비게이션, 푸터 같은 공통 UI를 넣기에 적합하다.
9. 공통 Layout 컴포넌트 분리
예제에서는 Layout 컴포넌트를 따로 만들고, 안에서 Header, Navbar를 조합했다.
export default function Layout({ children }) {
return (
<>
<Header />
<Navbar />
{children}
</>
);
}
정리
- 공통 구조를 별도 컴포넌트로 분리하면 재사용성이 좋아진다.
- layout.js는 페이지 구조를 연결하고, 실제 공통 UI는 컴포넌트로 나눌 수 있다.
- 관심사를 분리하면 코드가 더 명확해진다.
10. 중첩 레이아웃
Next.js는 특정 경로 아래에만 적용되는 레이아웃도 만들 수 있다.
예제에서는 dashboard 폴더 안에 별도의 layout.js를 두어 관리자 영역 전용 메뉴를 만들었다.
export default function DashboardLayout({ children }) {
return (
<>
<h3>대시보드 메뉴</h3>
<ul>
<li>통계</li>
<li>사용자</li>
<li>설정</li>
</ul>
{children}
</>
);
}
정리
- 폴더 안의 layout.js는 해당 경로 하위에만 적용된다.
- 전역 레이아웃과 중첩 레이아웃을 함께 사용할 수 있다.
- 관리자 페이지처럼 특정 영역 전용 구조를 만들 때 유용하다.
11. usePathname과 현재 경로 확인
현재 URL 경로를 확인할 때는 usePathname()을 사용한다.
const pathname = usePathname();
현재 경로와 메뉴 경로를 비교해서 활성 메뉴 스타일을 줄 수 있다.
const isActive = (path) => pathname === path;
<Link style={isActive('/menu') ? activeStyle : undefined} href="/menu">메뉴</Link>
정리
- usePathname()은 현재 경로를 문자열로 반환한다.
- 현재 보고 있는 페이지를 네비게이션에서 강조할 수 있다.
- 이런 훅은 클라이언트 컴포넌트에서만 사용할 수 있다.
12. useRouter로 프로그래밍 방식 이동
버튼 클릭 같은 동작으로 특정 페이지로 이동하고 싶을 때는 useRouter()를 사용한다.
const router = useRouter();
router.push(`/menu/search?menuName=${searchValue}`);
정리
- useRouter()는 코드로 경로를 이동할 수 있게 해준다.
- 검색 버튼 클릭 후 결과 페이지로 이동할 때 유용하다.
- router.push()를 사용하면 새로운 경로로 이동할 수 있다.
13. 쿼리 스트링과 useSearchParams
검색 결과 페이지에서는 쿼리 스트링 값을 읽기 위해 useSearchParams()를 사용했다.
const searchParam = useSearchParams();
const menuName = searchParam.get('menuName');
예를 들어 URL이 아래와 같다면
/menu/search?menuName=열무
menuName 값으로 열무를 얻을 수 있다.
정리
- 쿼리 스트링은 URL 뒤에 ?key=value 형태로 붙는 값이다.
- useSearchParams()를 사용하면 쿼리 값을 읽을 수 있다.
- 검색 기능이나 필터 기능에서 자주 사용된다.
14. 동적 라우팅과 useParams
폴더명을 [menuCode]처럼 대괄호로 만들면 동적 세그먼트를 받을 수 있다.
src/app/menu/[menuCode]/page.js
이 페이지에서는 useParams()로 URL 파라미터를 읽었다.
const { menuCode } = useParams();
예를 들어 /menu/3으로 접속하면 menuCode는 "3"이 된다.
정리
- 대괄호 폴더는 동적 라우트를 의미한다.
- useParams()로 URL의 동적 값을 읽을 수 있다.
- 상세 페이지를 만들 때 매우 자주 사용된다.
15. 데이터 기반 상세 페이지
메뉴 상세 페이지에서는 URL에서 받은 menuCode를 기준으로 메뉴 데이터를 찾아 출력했다.
useEffect(() => {
setMenu(getMenuByMenuCode(menuCode));
}, []);
return (
menu &&
<>
<h1>{menu.menuName} 상세페이지!</h1>
<h3>메뉴 가격: {menu.menuPrice}</h3>
<h3>메뉴 종류: {menu.categoryName}</h3>
<h3>메뉴 설명: {menu.detail.description}</h3>
<img src={menu.detail.image} style={{ maxWidth: 500 }} />
</>
);
정리
- 상세 페이지는 URL 파라미터와 데이터를 연결하는 구조로 만든다.
- 먼저 params를 읽고, 그 값으로 데이터를 찾아 상태에 저장한다.
- 준비가 끝난 뒤 조건부 렌더링으로 화면을 출력할 수 있다.
16. 데이터 모듈 분리
예제에서는 메뉴 데이터를 다루는 함수들을 MenuAPI.js로 분리했다.
export function getMenuList() {
return menus;
}
export function getMenuByMenuCode(menuCode) {
return menus.filter(menu => menu.menuCode === parseInt(menuCode))[0];
}
export function searchMenu(searchMenuName) {
return menus.filter(menu => menu.menuName.match(searchMenuName));
}
- 데이터 조회 로직도 컴포넌트 밖으로 분리할 수 있다.
- 컴포넌트는 화면과 상태에 집중하고, 데이터 관련 로직은 별도 파일에서 관리할 수 있다.
- 코드 역할이 분리되어 유지보수가 쉬워진다.
17. 오늘 배운 핵심 정리
- custom hook은 반복되는 Hook 로직을 재사용 가능한 함수로 분리하는 방법이다.
- useFetch처럼 데이터, 로딩, 에러 상태를 함께 묶어 관리할 수 있다.
- AbortController를 사용하면 컴포넌트 언마운트 시 fetch 요청을 취소할 수 있다.
- Next.js App Router는 폴더와 파일 구조를 기반으로 라우팅을 만든다.
- page.js는 실제 페이지를 의미하고, 폴더명이 URL 경로가 된다.
- Link를 사용하면 새로고침 없이 페이지 이동이 가능하다.
- layout.js를 사용하면 여러 페이지에 공통 레이아웃을 적용할 수 있다.
- 중첩 레이아웃을 사용하면 특정 경로 하위에만 별도 레이아웃을 만들 수 있다.
- usePathname()으로 현재 경로를 확인하고 활성 메뉴를 표시할 수 있다.
- useRouter()로 버튼 클릭 등 이벤트를 통해 프로그래밍 방식 라우팅을 할 수 있다.
- useSearchParams()로 쿼리 스트링 값을 읽을 수 있다.
- useParams()로 동적 라우트의 URL 파라미터를 읽을 수 있다.
- URL 파라미터를 기반으로 상세 페이지를 만들 수 있다.
- 데이터 조회 함수도 별도 모듈로 분리하면 구조가 더 깔끔해진다.
'TIL > [TIL]' 카테고리의 다른 글
| [TIL]React Query, json-server, Zustand persist, CRUD 테스트 (0) | 2026.04.24 |
|---|---|
| [TIL]Zustand를 활용한 전역 상태 관리와 Immer 개요 (0) | 2026.04.24 |
| [TIL]React API 통신과 데이터 컴포넌트 (0) | 2026.04.21 |
| [TIL]React Hooks 심화와 비동기 프로그래밍 (0) | 2026.04.20 |
| [TIL]React 반복 렌더링과 useEffect (0) | 2026.04.17 |