라우트
페이지 및 경로
`npm i react-router-dom @types/react-router-dom --save` React Router 를 사용해서 작성한다.
페이지 경로는 /~
도서상세만 /books/{id}
route 에서 존재하지 않는 에러페이지가 발생할 경우? `errorElement` 항목으로 예외처리한다.
오류 예외처리 Error.tsx
import { useRouteError } from 'react-router-dom';
interface RouteError {
statusText?: string;
message?: string;
}
function Error() {
const error = useRouteError() as RouteError;
return (
<div>
<h1>오류발생</h1>
<p>다음과 같은 오류가 발생했습니다</p>
<p>{error.statusText || error.message}</p>
</div>
);
}
export default Error;
RouteError 타입, useRouteError 메서드를 react-router-dom 에서 기본으로 제공한다.
화면깜빡임은 a 태그가 아닌 Link 태그로 대체하면 제대로 동작한다. 내 프로젝트에서는 window.location.href 를 사용하는데 이것또한 a 태그처럼 화면깜빡임이 일어나는 메서드!! 잘 모르고 js 기반에서 동작하는 함수라고 해서 사용했는데.. 전체적으로 수정하였다! onClick 이나 js 에서 필요할 경우 useNavigate() 메서드를 대신 사용하자!
app.tsx 에서 route가 어디에 적용되어 감싸졌는지 잘 체크해야 에러를 막을 수 있다. 근데 계속 element 를 layout 으로 감쌀수도 없는 모냥인데,,, 내 프로젝트에서는 route와 무관한 Layout.tsx이어서 route 외부에 두었다. route와 연관된 디자인컴포넌트일경우?
import Layout from './components/layout/Layout';
import Home from './pages/Home';
import { BookStoreThemeProvider } from './context/themeContext';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import Error from './components/common/Error';
const router = createBrowserRouter([
{
path: '/',
element: (
<Layout>
<Home />
</Layout>
),
errorElement: <Error />,
},
{
path: '/books',
element: (
<Layout>
<div>도서목록</div>
</Layout>
),
},
]);
function App() {
return (
<BookStoreThemeProvider>
<RouterProvider router={router} />
</BookStoreThemeProvider>
);
}
export default App;
프로젝트 모델 정의
interface 백엔드 데이터 타입 모델 작성하기
이때 전체 책 조회, 책 상세조회는 타입이 유사하기 때문에 확장하여 코드를 작성하는 것이 좋다.
export interface Book {
id: number;
title: string;
img: number;
category_id: number;
form: string;
isbn: string;
summary: string;
detail: string;
author: string;
pages: number;
contents: string;
price: number;
pubDate: string;
}
export interface BookDetail extends Book {
categoryName: string;
liked: boolean;
}
API 통신 모듈 구성
http.ts
import axios, { AxiosRequestConfig } from 'axios';
const BASE_URL = 'http://localhost:2222';
const DEFAULT_TIMEOUT = 30000;
export const createClient = (config?: AxiosRequestConfig) => {
const axiosInstance = axios.create({
baseURL: BASE_URL,
timeout: DEFAULT_TIMEOUT,
headers: {
'content-type': 'application/json',
},
withCredentials: true,
...config,
});
axiosInstance.interceptors.response.use(
(response) => {
return response;
},
(error) => {
return Promise.reject(error);
},
);
return axiosInstance;
};
export const httpClient = createClient();
카테고리 api 를 연결할때
import { Category } from '../models/category.model';
import { httpClient } from './http';
export const fetchCategory = async () => {
const response = await httpClient.get<Category[]>('category');
return response.data;
};
앞서 모델에서 지정한 모델 타입을 참조해서 서버의 데이터를 await 으로 받아온다.
요청할때마다 useState, useEffect 를 호출하는 방법도 있지만,
const [category, setCategory] = useState<Category[]>([]);
useEffect(() => {
fetchCategory().then((category) => {
setCategory(category);
});
}, []);
커스텀 hook 을 사용하는 방법도 있다! 리액트 기본 문법으로 데이터를 분리하거나 가공할때 많이 사용된다.
import { useEffect, useState } from 'react';
import { Category } from '../models/category.model';
import { fetchCategory } from '../api/category.api';
export const useCategory = () => {
const [category, setCategory] = useState<Category[]>([]);
useEffect(() => {
fetchCategory().then((category) => {
if (!category) return;
const categoryWithAll = [
{
category_id: null,
category_name: '전체',
},
...category,
];
setCategory(categoryWithAll);
});
}, []);
return { category };
};
/////////Header.tsx
const { category } = useCategory();
훅 장점!! useEffect, useState 를 분리해서 재사용성이 높고, 코드의 가독성을 챙길 수 있다.
프로젝트에 적극 적용해보고 싶다!
회원가입
이때 입력창 InputText 만든거 가져와서 스타일링 및 사용하는데, 이때 입력의 타입이 다르기 때문에, email, password 타입 선언하고, `React.InputHTMLAttributes<HTMLInputElement>` 의 타입 항복들을 그대로 사용할 수 있도록 한다.
///...
interface Props extends React.InputHTMLAttributes<HTMLInputElement> {
placeholder?: string;
inputType?: 'text' | 'email' | 'password' | 'number';
}
const InputText = React.forwardRef(
({ placeholder, inputType }: Props, ref: ForwardedRef<HTMLInputElement>) => {
//reactr 내장 input 컴포넌트
return (
<InputTextStyle placeholder={placeholder} ref={ref} type={inputType} />
);
},
);
const InputTextStyle = styled.input`
///...
`preventDefault()` : form 업데이트를 하면 기본적으로 페이지 이동을 제공하는데, 이동을 막는 메서드이다.
///...
const Signup = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
console.log(email, password);
};
return (
<>
<Title size="large">회원가입</Title>
<SignupStyle>
<form onSubmit={handleSubmit}>
<fieldset>
<InputText
placeholder="이메일"
inputType="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</fieldset>
<fieldset>
<InputText
placeholder="비밀번호"
inputType="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</fieldset>
<fieldset>
<Button type="submit" size="medium" scheme="primary">
회원가입
</Button>
</fieldset>
///...
React Hook Form 상태관리
기본적으로 interface가 필요하고, 사용법은 아래와 같다.
//...
interface SignupProps {
email: string;
password: string;
}
const Signup = () => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<SignupProps>();
const onSubmit = (data: SignupProps) => {
console.log(data);
};
return (
<>
<Title size="large">회원가입</Title>
<SignupStyle>
<form onSubmit={handleSubmit(onSubmit)}>
<fieldset>
<InputText
placeholder="이메일"
inputType="email"
{...register('email', { required: true })}
/>
{errors.email && (
<p className="error-text">이메일을 입력해주세요.</p>
)}
</fieldset>
//...
스프레드 연산자로 데이터를 받아와서 상태관리 한다.
회원가입 api 요청
입력받은 email과 password 값을 api 연결을 fetch 로 진행한다.
import { SignupProps } from '../pages/Signup';
import { httpClient } from './http';
export const signup = async (userData: SignupProps) => {
const response = await httpClient.post('/users/join', userData); //데이터를 바디로 보내준다
return response.data;
};
회원가입 버튼을 눌렀을때 (onSubmit) 네트워크를 통해 Payload body 안에 이메일과 비밀번호가 보내진다.
const onSubmit = (data: SignupProps) => {
signup(data).then((res) => {
//성공
window.alert('회원가입이 완료되었습니다.');
navigate('/login');
});
};
`window.alert` 간편하지만 커스텀 디자인하기에는 어렵다. 커스텀 훅으로 useAlert 를 사용하면 가능하다. 유지보수성에 좋다.
import { useCallback } from 'react';
export const useAlert = () => {
const showAlert = useCallback((message: string) => {
window.alert(message);
}, []);
return showAlert;
};
☑️ 배운 점
문윤기 강사님 수업이 들을수록 너무 좋다...
많이 배웠고, 덕분에 다음주 마감에 있는 플젝에 적용할 수 있을 것 같다.