[3차] 팩트폭행 MBTI 테스트 프로젝트 - 리팩토링(API,상태 관리)
REACT MBTI TEST PROJECT
베포 링크
프로젝트 설명
팩트로 폭행하는 MBTI TEST 입니다. F는 상처받을 수 있으니 테스트 진행 시 주의 요망
STACK
프로젝트 구조
- 3차에선 리팩토링을 진행했습니다.
- 깃에서
refactor/general
브랜치를 통해 확인 가능
전체 프로젝트 구조
📦src┣ 📂api
┃ ┣ 📜authAPI.js
┃ ┗ 📜mbtiAPI.js
┣ 📂assets
┃ ┣ 📂img
┃ ┃ ┣ 📂mbti
┃ ┗ 📜react.svg
┣ 📂components
┃ ┣ 📂footer
┃ ┣ 📂header
┃ ┣ 📜GlobalStyle.jsx
┃ ┣ 📜Layout.jsx
┃ ┣ 📜ProtectedRoute.jsx
┃ ┣ 📜TestForm.jsx
┃ ┣ 📜TestResultItem.jsx
┃ ┗ 📜TestResultList.jsx
┣ 📂constants
┃ ┗ 📜queryKeys.js
┣ 📂context
┃ ┗ 📜AuthContext.jsx
┣ 📂data
┃ ┗ 📜questions.js
┣ 📂hooks
┃ ┣ 📜mutations.jsx
┃ ┗ 📜queries.jsx
┣ 📂instance
┃ ┗ 📜baseInstance.js
┣ 📂pages
┃ ┣ 📜Join.jsx
┃ ┣ 📜Login.jsx
┃ ┣ 📜Main.jsx
┃ ┣ 📜Mypage.jsx
┃ ┣ 📜TestPage.jsx
┃ ┗ 📜TestResultPage.jsx
┣ 📂shared
┃ ┗ 📜Router.jsx
┣ 📂utils
┃ ┣ 📜dateResult.js
┃ ┣ 📜mbtiCalculator.js
┃ ┣ 📜mbtiImg.js
┃ ┗ 📜mbtiResult.js
┣ 📂zustand
┃ ┗ 📜userStore.js
┣ 📜App.css
┣ 📜App.jsx
┣ 📜index.css
┗ 📜main.jsx
프로젝트 세팅 및 기능 설명
4-1) 설치 패키지
- Json-server
- Tanstack query
- zustand
- Axios
- Tailwind
- styled-component
- react-router-dom
- the-new-css-reset
- lucide-react
4-2) 주요 기능 소개
- 메인 페이지 (1차에서 완료)
- 약간의 어그로성 문구와
테스트하기
버튼을 통해 로그인 페이지로 이동합니다.
- 약간의 어그로성 문구와
- 회원가입 페이지
- 닉네임, 아이디, 비밀번호 입력 후 회원가입 버튼을 클릭하면 “회원가입이 완료되었습니다.” 문구와 함께
로그인
페이지로 이동합니다.
- 닉네임, 아이디, 비밀번호 입력 후 회원가입 버튼을 클릭하면 “회원가입이 완료되었습니다.” 문구와 함께
- 로그인 페이지
- 아이디와, 비밀번호를 입력 후 로그인 버튼을 클릭하면 “로그인이 완료되었습니다.” 문구와 함께
메인
페이지로 이동합니다.
- 아이디와, 비밀번호를 입력 후 로그인 버튼을 클릭하면 “로그인이 완료되었습니다.” 문구와 함께
- 프로필 페이지
- 아이디, 이전 닉네임을 확인할 수 있으며, 변경 닉네임에 값을 입력 후 닉네임을 변경 버튼을 클릭하면 변경된 닉네임으로 확인이 가능합니다.
- 테스트 페이지
- 12개의 문항에 대해 체크 후 저장 버튼을 클릭하면, MBTI 테스트 결과가 화면에 출력됩니다.
- 결과보기 페이지
- MBTI 테스트 결과 리스트가 있는 페이지입니다.
- 게시물은 본인이 쓴 게시물만 공개/비공개, 삭제 처리가 가능합니다.
- 비공개 선택 시 해당 글은 결과보기 페이지에서 본인만 확인이 가능합니다.
작업 목록
5-1) 효율적인 API 관리
queryKeys.js
// constants/queryKeys.js
export const QUERY_KEYS = {
MBTI: "mbti",
REGISTER: "register",
LOGIN: "login",
USER: "user",
PROFILE: "profile"
}
// api/...API.js
// API 요청 함수 정리되어 있음
mutations.jsx
// hooks/mutations.jsx
export const useDeleteMbti = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: deleteTestResult,
onSuccess: () => {
queryClient.invalidateQueries([QUERY_KEYS.MBTI]);
}
})
}
export const useVisibilityMbti = () => {
const queryClient = useQueryClient();
return useMutation({
// mutationFn : 인자를 1개만 받을 수 있음
mutationFn: updateTestResultVisibility,
onSuccess: () => {
queryClient.invalidateQueries([QUERY_KEYS.MBTI]);
}
})
}
export const useRegisterUser = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: handleUserRegister,
onSuccess: () => {
queryClient.invalidateQueries([QUERY_KEYS.REGISTER]);
}
})
}
export const useLoginUser = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: handleUserLogin,
onSuccess: () => {
queryClient.invalidateQueries([QUERY_KEYS.LOGIN]);
}
})
}
export const useGetProfile = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: getUserProfile,
onSuccess: () => {
queryClient.invalidateQueries([QUERY_KEYS.USER]);
}
})
}
export const useUpdateProfile = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: updateProfile,
onSuccess: () => {
queryClient.invalidateQueries([QUERY_KEYS.PROFILE]);
}
})
}
queries.jsx
// hooks/queries.jsx
import { getUserProfile } from "@/api/authAPI";
import { getTestResults } from "@/api/mbtiAPI";
import { QUERY_KEYS } from "@/constants/queryKeys";
import { useQuery } from "@tanstack/react-query";
export const useMbti = () => useQuery({
queryKey: [QUERY_KEYS.MBTI],
queryFn: getTestResults,
})
export const useUser = () => useQuery({
queryKey: [QUERY_KEYS.USER],
queryFn: getUserProfile
})
Login.jsx
// Login.jsx
// 수정 전
const handleLogin = async (e) => {
e.preventDefault();
try{
const loginData = { id, password }
const response = await handleUserLogin(loginData);
if(response.success){
alert("로그인되었습니다. 메인 페이지로 이동합니다.");
login(response.accessToken);
navigate("/");
}
}catch(e){
console.log("Login Error =>", e);
alert("로그인이 실패했습니다.")
}
}
// 수정 후
const { mutate: loginUser, isError } = useLoginUser();
if(isError) return <div>에러발생 에러발생.</div>
const handleLogin = (e) => {
e.preventDefault();
const loadingData = { id, password };
loginUser(loadingData, {
onSuccess: (response) => {
alert(`로그인에 성공했습니다. 메인 페이지로 이동합니다.`);
login(response.accessToken);
navigate("/");
},
onError: (error) => {
alert(error.response?.data?.message || "로그인에 실패했습니다.")
}
})
}
Join.jsx
// Join.jsx
// [수정 전]
const handleJoin = async (e) => {
e.preventDefault();
try{
// 작성한 값
const joinData = {id, password, nickname}
// 해당 함수를 통해 데이터를 db에 전송함
const response = await handleUserRegister(joinData);
if(response.success) {
alert("회원가입이 완료되었습니다. 로그인 페이지로 이동합니다.")
navigate("/login");
}else{
alert(response.message || "회원가입이 실패했습니다.");
}
}catch(e){
console.log("Join Error =>", e.response || e);
alert("회원가입을 실패했습니다.")
}
}
// [수정 후]
const handleJoin = (e) => {
e.preventDefault();
const joinData = {id, password, nickname}
registerUser(joinData, {
onSuccess: (response) => {
alert(`${response.message}, 로그인 페이지로 이동합니다.`)
navigate("/login");
},
onError: (error) => {
alert(error.response?.data?.message || "회원가입에 실패했습니다.");
}
})
}
Trouble Shooting
작성중
회고
이번 리팩토링을 진행하며 개선된 점은 아래와 같다.
- queryKey
- 데이터를 효율적으로 관리하고 쉽게 재사용 할 수 있게됌
- API 인스턴스 설정 파일
- 별도 파일로 관리하며, API 호출의 일관성을 유지하고 한 곳에서 수정이 가능해서 용이함
- API 요청 함수
- 해당 함수를 분리하여 코드의 재사용성을 높이고, API 호출 로직을 명확하게 처리하게됌
- 커스텀 훅
- 복잡한 로직을 각각의 컴포넌트에서 분리하고 상태관리와 API 요청을 이전보다 간결하게 처리함
리팩토링을 통해 유저 API와 MBTI API 관련 로직을 명확히 분리하여 각 기능에 맞는 커스텀 훅을 생성하였고, 이로 인해 복잡한 로직을 간결하게 유지할 수 있었다. 중복 코드를 줄이고, 향후 유지보수에도 유리할 것이라 생각하고 특히 팀 프로젝트에서 이러한 구조는 유용할 것 같다고 생각함.
Keep - 현재 만족하고 있는 부분
- 중복 코드를 제거함
- 커스텀 훅을 사용해서 복잡한 로직을 간결하게 처리해봄
- 코드의 일관성을 유지하고 유지보수가 좀 더 편리하게 수정해봄
Problem - 불편하게 느끼는 부분
- 문법적으로는 익숙해졌지만 동작 방식에 대해 완벽하게 이해하지 못하는 것 같음
- 컴포넌트에서는 간결해진 것 같은데, 폴더가 많이 복잡한 것 같음
Try - problem에 대한 해결책, 당장 실행 가능한 것
- 리팩토링을 통해 효율적으로 관리할 수 있게 된 것 같지만 나의 코드를 처음 보는 사람들 입장에서 조금 더 쉽게 이해할 수 있도록 커스텀 훅과 각 기능별로 문서화를 하면 어떨까란 생각을 해봄