2024.07.15 프로그래머스 - React 동적 UI 개발 4
React 기반 동적 UI 개발 - 4
이번 시간에 주로 배운 기술
- React-Router (V6)
- Axios
- React-hook-form
React-Router 사용하기
설치
1
npm install react-router-dom @types/react-router-dom --save
적용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import React, { useContext, useState } from "react";
import Home from "./pages/Home";
import Detail from "./pages/Detail";
import Layout from "./Layout/Layout";
import { GlobalStyle } from "./style/global";
import { ThemeProvider } from "styled-components";
import { darkTheme, lightTheme, ThemeName, getTheme } from "./style/theme";
import ThemeSwitcher from "./shared/components/Header/ThemeSwitcher/ThemeSwitcher";
import { BookStoreThemeProvider, ThemeContext } from "./context/theme";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import Error from "./shared/components/Error";
const router = createBrowserRouter([
{
path: "/",
element: <Layout />,
errorElement: (
<Layout>
<Error />
</Layout>
),
children: [
{ index: true, element: <Home /> },
{ path: "/books", element: <div>도서목록</div> },
{ path: "/signup", element: <Signup /> },
],
},
]);
function App() {
// const [themeName, setThemeName] = useState<ThemeName>("light");
const { themeName, toggleTheme } = useContext(ThemeContext);
return (
<BookStoreThemeProvider>
<Layout>
<RouterProvider router={router} />
</Layout>
</BookStoreThemeProvider>
);
}
export default App;
createBrowserRouter
를 import 하여, router 를 설정- 기본 Path 를 “/” 로 설정 후 이어지는 url 에 대해 children 선언
- index 속성은 부모의 경로로 접근했을 때, 기본으로 보여줄 Route를 선언
- 이후 추가 url에 대해서는 Root Route 경로의 children으로 작성 하여 Layout 컴포넌트를 템플릿 엘리먼트로 활용
Layout.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
import React, { memo } from "react"; import Headers from "../shared/components/Header/Headers"; import Footer from "../shared/components/Footer"; import styled from "styled-components"; import { Outlet } from "react-router-dom"; interface Props { children?: React.ReactNode; } function Layout({ children }: Props) { return ( <> <Headers /> <LayoutStyle>{children || <Outlet />}</LayoutStyle> <Footer /> </> ); } const LayoutStyle = styled.main` width: 100%; margin: 0 auto; max-width: ${({ theme }) => theme.layout.width.large}; padding: 20px 0; `; export default memo(Layout);
- Outlet 기능을 통해, Layout으로 Route ELement에 대한 템플릿으로 제공,
- children props가 전달 될 때는, Outlet이 아닌 children 엘리먼트를 렌더링
- ⇒ Error Element는 Root Router에 지정하였으므로 children으로 전달해야 해서 위와 같은 방법으로 작성
Axios 사용하기
1
npm i axios
api>http.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
import axios, { AxiosRequestConfig } from "axios"; const BASE_URL = "http://localhost:8888"; const DEFAULT_TIMEOUT = 30000; export const createClient = (config?: AxiosRequestConfig) => { const axiosInstace = axios.create({ baseURL: BASE_URL, timeout: DEFAULT_TIMEOUT, headers: { "Content-Type": "application/json", }, withCredentials: true, ...config, }); axiosInstace.interceptors.response.use( (response) => { return response; }, (error) => { return Promise.reject(error); } ); return axiosInstace; }; export const httpClient = createClient();
- BASE_URL : Axios로 http 통신을 할 떄, 기본으로 입력될 url 경로
- DEFAULT_TIMEOUT : timeout 시간
AxiosRequestConfig 를 통해 기본 요청에 대한 옵션 값을 설정
axiosInstance
에 axios.create를 통해 요청 템플릿 작성axiosInstance.interceptors 를 통해, 정상 응답과, 에러 상황에 대한 반환 값 작성
에러
Re-exporting a type when ‘isolatedModules’ is enabled requires using ‘export type’
위 에러는 model 폴더 내에서 여러 [이름].model.ts 파일을 공통으로 export 하다가 발생한 에러다.
model> intex.ts
1 2 3 4 5 6 7 8 9
import { Book, BookDetail } from "./book.model"; import { Category } from "./category.model"; import { User } from "./user.model"; import { Order } from "./order.model"; import { Pagination } from "./pagination.model"; import { Cart } from "./cart.model"; export { Book, BookDetail, Category, User, Order, Pagination, Cart };
문제의 원인은 에러 메세지 자체에서 알수 있듯이 isolatedModules
가 enable로 설정되어 있기 때문에 발생하는 에러인데, 이는 interface를 re-exporting 을 하려고 할 때 export type
을 통해 해당 타입성 변수를 export 해야 한다고 한다.
변경한 코드
1 2 3 4 5 6 7 8 9
import { Book, BookDetail } from "./book.model"; import { Category } from "./category.model"; import { User } from "./user.model"; import { Order } from "./order.model"; import { Pagination } from "./pagination.model"; import { Cart } from "./cart.model"; export type { Book, BookDetail, Category, User, Order, Pagination, Cart };
커스텀 훅
category.model.ts
1 2 3 4 5
export interface Category { id: number | null; name: string; }
api>http.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
import axios, { AxiosRequestConfig } from "axios"; const BASE_URL = "http://localhost:8888"; 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>category.api.ts
1 2 3 4 5 6 7
import { Category } from "../models"; import { httpClient } from "./http"; export const fetchCategory = async () => { const response = await httpClient.get<Category[]>("/category"); return response.data; };
실제 Category 데이터를 fetch 해 오는 작업을 수행하는 함수!
hooks>useCategory
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
import { useEffect, useState } from "react"; import { Category } from "../shared/models"; import { fetchCategory } from "../shared/api/category.api"; export const useCategory = () => { const [categories, setCategories] = useState<Category[]>([]); useEffect(() => { fetchCategory().then((category) => { console.log(category); if (!category) return; const categoryWithAll = [{ id: null, name: "전체" }, ...category]; setCategories(categoryWithAll); }); }, []); return { categories }; };
category.api.ts 의 fetchCategory를 import 하여, 해당 fetcher에 대한 동작의 완료에 대한 보상을 받는다.
때문에 해당 커스텀 훅이 return 하는 것은 useCategory 자체의 state인 categories를 return 하여
해당 커스텀 훅을 반환 받은 변수는 위의 fetcher 를 사용한 category data를 할당받을수 있음을 의미 한다.
Header.tsx
1 2 3
function Headers() { const { categories } = useCategory(); return (...)
실제 컴포넌트에서 사용할 때 위 코드 처럼 커스텀 훅을 import 하여 실행 형태로 할당 하면
해당 커스텀 훅이 반환하는 categories 값을 할당 받아 사용한다.