npm install react-router-dom @types/react-router-dom --save
App.tsx에서 react-router-dom의 createBrowserRouter와 RouterProvider를 사용해 라우트를 설정const router = createBrowserRouter([
{
path: "/",
element: <Layout><Home/></Layout>,
errorElement: <Error/>
},
{
path: "/books",
element: <Layout><div>도서 목록</div></Layout>
},
{
path: "/signup",
element: <Layout><Signup/></Layout>
}
// 앞으로 /login, /reset, /cart, /orderList 등을 추가할 수 있습니다.
]);
function App() {
return (
<BookSotreThemeProvider>
<ThemeSwitcher/>
<RouterProvider router={router}/>
</BookSotreThemeProvider>
);
}
라우터 목록
1. 로그인:/login
2. 회원가입:/signup
3. 비밀번호 초기화:/reset
4. 도서목록:/books
5. 도서 상세:/books/{id}
6. 장바구니:/cart
7. 주문목록:/orderList
위와 같이 홈(/), 도서목록(/books) 등등의 라우터 목록을 생성하고,
에러 발생 시 보여줄 Error 컴포넌트도 지정할 수 있다.
이전에 만든 서버에서 응답형태를 보고 API통신때 사용할 수 있도록 응답형식에 맞게 미리 정리해야한다.
/models파일에 다음과 같이 파일을 만들어서 정리해었다.
// user.model.ts
export interface User {
id: number;
username: string;
email: string;
password?: string;
}
// book.model.ts
export interface Book {
id: number;
title: string;
author: string;
categoryId: number;
price: number;
description?: string;
}
// category.model.ts
export interface Category {
id: number;
name: string;
}
// cart.modal.ts
export interface CartItem {
bookId: number;
quantity: number;
}
// order.model.ts
export interface Order {
id: number;
userId: number;
items: CartItem[];
total: number;
status?: string;
}
// pagenation.model.ts
export interface Page<T> {
content: T[];
page: number;
size: number;
totalElements: number;
totalPages: number;
}

UI를 보여줄 view 컴포넌트로 부터 Hook이 API호출을 받으면, 이걸 백엔드 서버에 보낸다는 흐름이다.
이를 위해서 HTTP 클라이언트를 api/http.ts파일에 작성해주었다.
import axios, { AxiosRequestConfig } from 'axios';
const BASE_URL = "http://localhost:9999";
const DEFAULT_TIMEOUT = 30000;
export const createClient = (config?: AxiosRequestConfig) => {
const instance = axios.create({
baseURL: BASE_URL,
timeout: DEFAULT_TIMEOUT,
headers: { "content-type": "application/json" },
withCredentials: true,
...config,
});
instance.interceptors.response.use(
response => response,
error => Promise.reject(error)
);
return instance;
};
export const httpClient = createClient();
이는 모든 httpClient를 통해 일정하게 호출하게 된다.
이후 API함수를 만들어 view 에서 사용할 수 있도록 만들어주었다.
회원가입을 구현하기 위해서 React-hook-form을 사용하였다.
React hook form
React에서 폼 상태 관리를 간편하고성능 좋게 해주는 라이브러리이다.
- 간편한 폼 관리: register, handleSubmit, errors 제공
- 유효성 검사: required, pattern 등 옵션으로 간단 설정
- 타입 안전성: 제네릭으로 폼 데이터 타입 지정 가능
위 구조를 바탕으로, 라우팅에서부터 모델·API·UI까지 깔끔한 계층 구조를 갖춘 React 애플리케이션을 구현할 수 있습니다.
// src/pages/Signup.tsx
import React from "react";
import { useForm } from "react-hook-form";
import { signup } from "../api/auth.api";
import InputText from "../components/common/InputText";
import Button from "../components/common/Button";
import { useAlert } from "../hooks/useAlert";
export interface SignupProps {
username: string;
email: string;
password: string;
}
const Signup: React.FC = () => {
const { register, handleSubmit, formState: { errors } } = useForm<SignupProps>();
const { showAlert } = useAlert();
const onSubmit = async (data: SignupProps) => {
try {
await signup(data);
showAlert("회원가입이 완료되었습니다.");
} catch (err) {
showAlert("회원가입에 실패했습니다.");
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<InputText
label="아이디"
{...register("username", { required: "아이디는 필수 입력입니다." })}
error={errors.username?.message}
/>
<InputText
type="email"
label="이메일"
{...register("email", { required: "이메일은 필수 입력입니다." })}
error={errors.email?.message}
/>
<InputText
type="password"
label="비밀번호"
{...register("password", { required: "비밀번호는 필수 입력입니다." })}
error={errors.password?.message}
/>
<Button type="submit" size="large" scheme="primary">
회원가입
</Button>
</form>
);
};
export default Signup;
useForm<SignupProps>()로 폼 데이터 타입을 지정handleSubmit(onSubmit)으로 유효성 검사 후 API 호출useAlert로 나중에 window.alert말고 다른 라이브러리고 alert를 래핑하기 편하게 만듬