CRACO를 이용할 것임 (환경들을 변경하는 작업)
npm i -D @craco/craco
npm install -D craco-alias
터미널에 위 명령을 적어 설치해줌
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}
tsconfig.paths.json 파일 생성
const cracoAlias = require('craco-alias');
module.exports = {
plugins: [
{
plugin: cracoAlias,
options: {
source: 'tsconfig',
baseUrl: '.',
tsConfigPath: 'tsconfig.paths.json',
debug: false,
}
}
]
}
craco.config.js 파일 생성
"extends": "./tsconfig.paths.json",
"include": ["src", "craco.config.js"]
tsconfig.json에 다음 내용 추가
import { Book } from '../../models/book.model';
import { getImgSrc } from '../../utils/image';
import { formatNumber } from '../../utils/format';
// 위와 같은 경로를 아래처럼 수정할 수 있다
import { Book } from '@/models/book.model';
import { getImgSrc } from '@/utils/image';
import { formatNumber } from '@/utils/format';
const router = createBrowserRouter(
routeList.map((item) => {
return {
...item,
element: <Layout>{item.element}</Layout>,
errorElement: <Error />
};
})
);
App.tsx 파일의 라우터 설정의 중복 코드를 수정한다.
// 공통 요청 부분
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.ts의 코드를 수정하여 api에서의 중복 코드를 공통 모듈로 만든다
React와 TypeScript를 사용하여 프로젝트를 개발할 때, 반복적으로 작성해야 하는 코드 패턴이 많다. 이러한 반복 작업을 줄이고, 생산성을 높이기 위해 코드 스니펫을 사용하는 것이 매우 유용하다.

위와 같은 extension을 설치하여 더욱 편리하게 스니펫 생성이 가능하다. $1, $2 등으로 키보드 입력 순서를 결정할 수 있다.
{
"Component Base": {
"prefix": "comp_base",
"body": [
"import { styled } from 'styled-components';",
"",
"function ${TM_FILENAME_BASE}() {",
" return (",
" <${TM_FILENAME_BASE}Style></${TM_FILENAME_BASE}Style>",
" );",
"}",
"",
"const ${TM_FILENAME_BASE}Style = styled.div`",
"`;",
"",
"export default ${TM_FILENAME_BASE}"
]
}
}
{
"_comp": {
"prefix": "_comp",
"body": "import { styled } from 'styled-components';\r\n\r\nfunction $1() {\r\n return (\r\n <$1Style></$1Style>\r\n );\r\n}\r\n\r\nconst $1Style = styled.div`\r\n`;\r\n\r\nexport default $1;",
"description": "컴포넌트 작성"
}
}
{
"_theme_val": {
"prefix": "_theme_val",
"body": "${({theme}) => theme.$1};",
"description": "테마 값 참조"
}
}
{
"_effect": {
"prefix": "_effect",
"body": "useEffect(() => {\r\n $2\r\n }, [$1]);",
"description": "useEffect 훅"
}
}
{
"_title": {
"prefix": "_title",
"body": "<Title size='$1' color='$2'>\r\n $3\r\n</Title>",
"description": "타이틀 컴포넌트"
}
}
{
"_flex": {
"prefix": "_flex",
"body": "display: flex;\r\nalign-items: start;\r\njustify-content: space-between;\r\ngap: $1;",
"description": "CSS Flex 레이아웃"
}
}
import { login, resetPassword, resetRequest, signup } from "@/api/auth.api";
import { LoginProps } from "@/pages/Login";
import { useAuthStore } from "@/store/authStore";
import { useAlert } from "./useAlert";
import { useNavigate } from "react-router-dom";
import { SignupProps } from "@/pages/Signup";
import { useState } from "react";
export const useAuth = () => {
const { showAlert } = useAlert();
const navigate = useNavigate();
// 상태
const { storeLogin } = useAuthStore();
// 메소드
const userLogin = (data: LoginProps) => {
login(data).then((res) => {
// 상태 변화
storeLogin(res.token);
console.log(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
};
};
react-query는 굉장히 많이 사용되고 있는 서버 state 라이브러리임.
npm i react-query --save
react-query의 장점은 자동으로 데이터를 동기화해주는 역할을 한다는 것이다. 화면에 대한 부가적인 기능도 지원하며, 서버와의 요청값을 요청하는 cost를 아낄 수 있다. 선언적 데이터는 물론이고 로딩 상태나, 에러 상태, 에러 핸들링을 기본적으로 제공하여 화면 렌더값을 만들기에 편리하다. 개발자로서 생산성이 향상되며 코드의 길이도 짧아진다. 서비스 전반적으로 fetch나 변화 부분을 react-query로 관리하면 일관된 구조를 지킬 수 있어서 데이터의 흐름을 통제하는 부분에 있어서 굉장히 유용한 도구이다.
// queryClient.ts
import { QueryClient } from "react-query";
export const queryClient = new QueryClient();
// App. tsx
function App() {
return (
<QueryClientProvider client={queryClient}>
<BookStoreThemeProvider>
<RouterProvider router={router} />
</BookStoreThemeProvider>
</QueryClientProvider>
);
}
// useBooks.ts
import { useLocation } from "react-router-dom";
import { fetchBooks } from "../api/books.api";
import { QUERYSTRING } from "../constants/querystring";
import { LIMIT } from "../constants/pagination";
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,
newBook:
params.get(QUERYSTRING.NEWS) ?
true : undefined,
currentPage:
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
};
};