import alias
현재 import 경로는 상대 경로로 지정되어 있다. 프로젝트 규모가 커졌을 때 상대 경로로 지정되어 있으면 유지보수가 힘들어지므로 절대 경로로 바꿔보자.
vite.config.ts
vite.config.ts 파일을 생성하고 아래와 같은 코드를 입력한다.
alias 부분에 별칭을 작성한다.
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
resolve: {
alias: [
{ find: '@', replacement: '/src' }
]
}
})
tsconfig.json
이 파일에 절대 경로를 알려주기 위해 다음과 같이 설정한다.
{
"compilerOptions": {
// ...
/* 경로 설정 */
"baseUrl": "src",
"paths": {
"@/*": [ "*" ]
}
},
// ...
}
전(상대 경로) | 후(절대 경로) |
import Layout from "./components/layout/Layout" | import Layout from "@/components/layout/Layout" |
이제 위와 같이 절대 경로를 사용할 수 있다.
중복 제거
라우트 등록
원래는 라우트의 각 element가 <Layout><Home /></Layout>과 같은 형태로 작성되어 있어서 Layout 부분이 중복됐었다.
따라서 라우트 리스트 배열을 map으로 순회하며 element를 Layout의 children으로 넣어서 반복되는 코드를 줄인다.
const routeList = [
{
path: '/',
element: <Home />
},
...
{
path: '/orderList',
element: <OrderList />
}
];
const router = createBrowserRouter(
routeList.map((item) => {
return {
...item,
element: <Layout>{item.element}</Layout>,
errorElement: <Error />
};
})
);
Http Request
http.ts 파일에 다음 코드를 추가한다.
type RequestMethod = 'get' | 'post' | 'put' | 'delete';
export const requestHandler = async <T>(method: RequestMethod, url: string, payload?: T) => {
let response;
switch (method) {
case 'post':
response = await httpClient.post(url, payload);
break;
case 'get':
response = await httpClient.get(url);
break;
case 'put':
response = await httpClient.put(url, payload);
break;
case 'delete':
response = await httpClient.delete(url);
break;
}
return response.data;
}
그러면 http 요청을 보내는 api를 다음과 같이 수정할 수 있다.
// Before
export const order = async (orderData: OrderSheet) => {
const response = await httpClient.post('/orders', orderData);
return response.data;
}
// After
export const order = async (orderData: OrderSheet) => {
return await requestHandler<OrderSheet>('post', '/orders', orderData);
}
스니펫 만들기
스니펫은 키워드와 템플릿을 설정하면 키워드를 입력했을 때 해당 템플릿이 입력되게 해준다.
즉, 자동완성 기능을 사용할 수 있게 해주기 때문에 반복 타이핑을 피할 수 있다.
1. vscode의 확장에서 스니펫을 설치한다.
2. 컴포넌트 이름이 들어가는 부분들에 $1을 입력한다. $1은 스니펫이 입력된 직후에 커서가 위치하는 곳이다.
참고로 $1에서 탭을 누르면 $2로 이동한다.
import styled from "styled-components";
function $1() {
return (
<$1Style>
<div>$1 body</div>
</$1Style>
);
}
const $1Style = styled.div``;
export default $1;
3. 등록할 코드를 드래그하고 우클릭해서 generate snippet 클릭한다.
4. 대응 언어를 선택한다.
5. 스니펫 이름을 입력한다.
6. 스니펫 트리거를 설정한다. 여기서 설정한 이름을 입력하면 실행이 된다.
7. 설명을 작성한다.
8. 트리거로 설정한 이름을 입력하면 등록했던 코드가 나타난다.
import styled from "styled-components";
function () {
return (
<Style>
<div> body</div>
</Style>
);
}
const Style = styled.div``;
export default ;
useAuth hook 만들기
컴포넌트 파일에 작성되어 있는 로그인, 회원가입, 비밀번호 초기화 관련 로직들을 훅으로 옮긴다.
export const useAuth = () => {
const { showAlert } = useAlert();
const navigate = useNavigate();
const { storeLogin, storeLogout, isLoggedIn } = useAuthStore();
const userLogin = (data: LoginProps) => {
login(data).then((res) => {
storeLogin(res.token);
showAlert('로그인 완료되었습니다.');
navigate('/');
}, (error) => {
showAlert('로그인이 실패했습니다.');
});
}
const userSignup = (data: SignupProps) => {
signup(data).then((res) => {
showAlert('회원가입 완료');
navigate('/login');
})
}
const userResetPassword = (data: SignupProps) => {
resetPassword(data).then(() => {
showAlert('비밀번호가 초기화 되었습니다.');
navigate('/login');
})
}
const [resetRequested, setResetRequested] = useState(false);
const userResetRequest = (data: SignupProps) => {
resetRequest(data).then(() => {
setResetRequested(true);
});
}
return { userLogin, userSignup, userResetPassword, userResetRequest, resetRequested };
}
아래 코드는 Login.tsx인데, 로그인 처리 로직을 분리했기 때문에 코드가 간결해진 것을 확인할 수 있다.
function Login() {
const { userLogin } = useAuth();
...
const onSubmit = (data: LoginProps) => {
userLogin(data);
};
return (
...
)
}
react-query
react-query는 자동으로 데이터 동기화 해주는 장점이 있다.
터미널에 다음의 명령어를 입력하여 설치한다.
npm i react-query --save
api/queryClient.ts
다음과 같이 기본 파일을 작성한다.
import { QueryClient } from "react-query";
export const queryClient = new QueryClient();
App.tsx
다음과 같이 최상위 컴포넌트를 QueryClientProvider로 감싸준다.
import { QueryClientProvider } from "react-query";
import { queryClient } from "./api/queryClient"
// ...
function App() {
return (
<QueryClientProvider client={queryClient}>
<BookStoreThemeProvider>
<RouterProvider router={router} />
</BookStoreThemeProvider>
</QueryClientProvider>
)
}
export default App;
hooks/useBooks.ts
도서 목록을 불러오는 훅을 수정한다.
useQuery의 첫 번째 파라미터는 [이름, 의존성]이다. url의 쿼리에 수정이 발생하면 데이터를 자동으로 업데이트해준다.
isLoading을 제공해주기 때문에 이 값을 export 해서 로딩 화면을 보여줄 수도 있다.
// ...
import { useQuery } from "react-query";
export const useBooks = () => {
const location = useLocation();
const params = new URLSearchParams(location.search);
const { data: booksData, isLoading: isBooksLoading } = useQuery(
['books', location.search],
() =>
fetchBooks({
category_id: params.get(QUERYSTRING.CATEGORY_ID) ?
Number(params.get(QUERYSTRING.CATEGORY_ID)) : undefined,
news: params.get(QUERYSTRING.NEWS) ? true : undefined,
current_page: params.get(QUERYSTRING.PAGE) ?
Number(params.get(QUERYSTRING.PAGE)) : 1,
limit: LIMIT
})
);
return {
books: booksData?.books,
pagination: booksData?.pagination,
isEmpty: booksData?.books.length === 0,
isBooksLoading
}
}
Loading.tsx
로딩 애니메이션을 적용하기 위해서 keyframe을 사용한다.
0% 일 때가 시작이고, 100%가 종료이다. 그래서 아래 코드는 0 degree부터 360 degree까지 해당 아이콘이 회전하는 애니메이션을 나타낸다. animation 속성으로 infinite를 적용했기 때문에 멈추지 않고 계속 돌아간다.
...
function Loading() {
return (
<LoadingStyle>
<FaSpinner />
</LoadingStyle>
);
}
const LoadingStyle = styled.div`
padding: 40px 0;
text-align: center;
@keyframes rotate {
100% {
transform: rotate(360deg);
}
}
svg {
width: 70px;
height: 70px;
fill: #ccc;
animation: rotate 1s linear infinite;
}
`;
export default Loading;
pages/Books.tsx
앞서 만든 훅에서 응답으로 받은 도서 목록, 페이지네이션 정보, 조회 결과가 있는지에 대한 여부, 로딩 완료 여부를 가져와서 각 상황에 맞는 컴포넌트를 렌더링 한다.
function Books() {
const { books, pagination, isEmpty, isBooksLoading } = useBooks();
if (isBooksLoading) {
return <Loading />;
}
return (
<>
<Title size='large'>도서 검색 결과</Title>
<BooksStyle>
<div className='filter'>
<BooksFilter />
<BooksViewSwitcher />
</div>
{
books && pagination &&
<>
<BooksList books={books} />
<Pagination pagination={pagination} />
</>
}
{isEmpty && <BooksEmpty />}
</BooksStyle>
</>
)
}
배운 점
- 프로젝트 규모가 커졌을 때 상대 경로로 지정되어 있으면 유지보수가 힘들어지므로 절대 경로로 설정하는 것이 좋다.
- 스니펫을 이용하면 자동완성 기능을 사용할 수 있게 해주기 때문에 반복 타이핑을 피할 수 있다.
- react-query를 사용하면 useEffect를 사용하지 않고도 자동으로 데이터 동기화할 수 있다.
'데브코스' 카테고리의 다른 글
[16주차 - DAY1] 도서 정보 사이트 - 리뷰 (0) | 2024.06.10 |
---|---|
[15주차 - DAY5] 스니펫 (0) | 2024.06.07 |
[15주차 주간 발표] Flex, Grid (0) | 2024.06.05 |
[15주차 - DAY3] 도서 정보 사이트 - 주문서 (0) | 2024.06.05 |
[15주차 - DAY2] 도서 정보 사이트 - 장바구니 목록 (0) | 2024.06.04 |