무려 5개월전에 끝내고 UI의 반응형 리팩토링 한번했던 프로젝트이다.
오랜만에 내용정리를 하려니 조금 기억에 무리가 있긴하지만, 기억을 되살려보며
내용정리를 하려고한다.
앞으로는 바로바로해야지...
보배빌림 프로젝트에서는 react-router-dom을 사용한 라우팅방식 즉,
Routes, Route를 사용하지 않고 라우팅방식을 구현했었다.
pages폴더의 index.jsx에 아래와 같이 코드를 작성하였다.
// pages/index.jsx
const PAGES = [
{
element: <Home />,
path: ROUTES.HOME.PATH,
name: ROUTES.HOME.NAME,
},
{
element: <Search />,
path: ROUTES.SEARCH.PATH,
name: ROUTES.SEARCH.NAME,
},
{// 아래로는 로그인상태일때 라우팅
element: <PrivateRouter />,
children: [
{
element: <Business />,
path: ROUTES.BUSINESS.PATH,
name: ROUTES.BUSINESS.NAME,
},
{
element: <Orders />,
path: ROUTES.ORDERS.PATH,
name: ROUTES.ORDERS.NAME,
},
{
element: <MyPage />,
path: ROUTES.MYPAGE.PATH,
name: ROUTES.MYPAGE.NAME,
},
{
element: <PaymentCompleted />,
path: ROUTES.PAYMENTCOMPLETED.PATH,
name: ROUTES.PAYMENTCOMPLETED.NAME,
},
{
element: <Payments />,
path: ROUTES.PAYMENTS.PATH,
name: ROUTES.PAYMENTS.NAME,
},
{
element: <Notice />,
path: ROUTES.NOTICE.PATH,
name: ROUTES.NOTICE.NAME,
},
{
element: <MyProfile />,
path: ROUTES.MYPROFILE.PATH,
name: ROUTES.MYPROFILE.NAME,
},
],
},
];
export default PAGES;
그리고 constants폴더에 routes.js에는
// constants/routes.js
const ROUTES = Object.freeze({
HOME: {
PATH: '/',
NAME: 'Home',
},
SIGNUP: {
PATH: '/signup',
NAME: 'Signup',
},
ADMIN_SIGNUP: {
PATH: '/adminsignup',
NAME: 'Adminsignup',
},
MYPAGE: {
PATH: '/mypage',
NAME: 'Mypage',
},
MYPROFILE: {
PATH: '/myprofile',
NAME: 'Myprofile',
},
NOTICE: {
PATH: '/notice',
NAME: 'Notice',
},
LOGIN: {
PATH: '/login',
NAME: 'Login',
},
LOGOUT: {
PATH: '/logout',
NAME: 'Logout',
},
SEARCH: {
PATH: '/search',
NAME: 'Search',
},
});
export default ROUTES;
위의 Object.freeze()에 대해 간략히 설명하자면
const와 Object.freeze()는 비슷하지만 완전히 다르게 동작한다.
const는 상수를 선언하므로 값의 재할당이 불가능하다.
하지만 const로 객체를 선언하고 값자체 변경은 불가능 하지만 속성을 재할당하는건 가능하다.
하지만 Object.freeze()매서드는 말그대로 동결시킨다는 의미이다.
속성까지 고정시킬수 있다.
Object.freeze()를 사용한 이유는 라우팅 path들을 고정?시키기 위함이었던 것 같다.
결론은 위처럼 라우팅방식을 구현하였다.
//pages/MyPage
import BottomNav from '../../components/@layout/BottomNav/BottomNav';
import MyPageForm from '../../components/MyPage/MyPageForm';
const MyPage = () => {
return (
<>
<MyPageForm />
<BottomNav /> // 하단바 컴포넌트
</>
);
};
export default MyPage;
// components/commons/PageWrapper
import { useNavigate } from 'react-router-dom'; // useNavigate() 훅 사용
import { ArrowIcon } from '@/assets';
import * as S from './PageWrapper.style';
const PageWrapper = ({
title,
path,
}) => {
const navigate = useNavigate();
return (
<S.Header>
{/* path 는 뒤로가기를 눌렀을때 이동할 페이지를 의미 합니다. */}
<div className='button' onClick={() => navigate(path)}>
<ArrowIcon />
</div>
<S.Title>{title}</S.Title>
</S.Header>
);
};
export default PageWrapper;
import { PageWrapper } from '../../components/@commons';
import BottomNav from '../../components/@layout/BottomNav/BottomNav';
import MyPageForm from '../../components/MyPage/MyPageForm';
const MyPage = () => {
return (
<>
<PageWrapper title={'마이페이지'} path={'/'}> //title에 '마이페이지', path에 home페이지 경로를 props로 내려준다.
<MyPageForm />
</PageWrapper>
<BottomNav />
</>
);
};
export default MyPage;
회원가입에는 이메일,비밀번호, 닉네임 등 입력양식form이 있어야한다.
React-Hook-form
라이브러리를 사용해보았다.
import { useForm } from 'react-hook-form';
const SignUp = () -> {
const {
register,
handleSubmit,
watch, // input에 입력된 값을 확인가능
setValue, // input에 입력된 값을 변경 & 할당 가능
formState: { errors },
} = useForm({
mode: 'onChange', // 필수적으로 써야함 & mode: onChange 를 써줘야 아래 errors도 확인(출력) 가능!
defaultValues: { // defaultValue 즉 기본값을 지정할 수 있다.
email: '',
},
});
const onValid = (data) => { // data는 input에 입력된 값들이 register고유명과 값으로 key값쌍인 객체이다. ex) {email: rrr@naver.com, password: 1234}
axios.post(`${api}/signup`, data)
.then(() => {
console.log(회원가입 성공!)
})
}
return (
<S.SignUpContainer>
<form onSubmit={handleSubmit(onValid, onInValid)}>
<input
type='email'
placeholder='이메일'
{...register('email', { // input의 고유명 'email' 지정
required: '⚠ E-mail 필수입력', // 입력창에 text를 썻다 지우거나 했을때 필수적으로 출력되는 메시지
pattern: {
value: EMAIL_REGEX, // 정규식 불러옴
message: '⚠ E-mail형식에 맞지 않습니다.', // 에러메시지
},
})}
<button>회원가입완료</button> // 버튼을 누르면 handleSubmit함수 실행
</form>
</S.SignUpContainer>
)
}
위처럼 React-Hook-Form을 사용하여 기본 틀은 구현 하였다.
// components/SignUp
import * as S from '/style.js';
import { useRecoilState, useSetRecoilState } from 'recoil';
import {recoilPostAddress, userInfoState} from '../../../recoil/userInfoState';
const SignUp = () => {
const [inSignAddress, setInSignAddress] = useRecoilState(recoilPostAddress);
const [inputState, setInputState] = useRecoilState(userInfoState);
return (
<>
<S.SearchAddressBtn
type='button'
onClick={() => {
setInputState(watch()); // 전역상태관리로 입력양식 저장
navigate('/searchaddress'); // 주소찾기 컴포넌트 경로로 이동
}}
>
주소찾기
</S.SearchAddressBtn>
</>
)
}
import DaumPostcode from 'react-daum-postcode';
import { useNavigate } from 'react-router-dom';
import { useSetRecoilState, useRecoilValue } from 'recoil';
import {
recoilPostAddress,
} from '../../../recoil/userInfoState';
const PostCode = (data) => {
const navigate = useNavigate();
const setInPostAddress = useSetRecoilState(recoilPostAddress);
const complete = (data) => {
let fullAddress = data.address;
let extraAddress = '';
if (data.userSelectedType === 'R') {
if (data.bname !== '') {
extraAddress += data.bname;
}
if (data.buildingName !== '') {
extraAddress +=
extraAddress !== '' ? `, ${data.buildingName}` : data.buildingName;
}
fullAddress += extraAddress !== '' ? `(${extraAddress})` : '';
}
if (data.userSelectedType === 'J') {
if (data.bname !== '') {
extraAddress += data.bname;
}
if (data.buildingName !== '') {
extraAddress +=
extraAddress !== '' ? `, ${data.buildingName}` : data.buildingName;
}
fullAddress += extraAddress !== '' ? `(${extraAddress})` : '';
}
setInPostAddress(fullAddress); // 주소값 전역상태저장
return <DaumPostcode onComplete={complete} />;
};
export default PostCode;
import { atom } from 'recoil';
const userInfoState = atom({ // 주소찾기 누를시 유저입력정보 전역상태저장소
key: 'userInfoState',
default: {
email: '',
password: '',
nickname: '',
phone: '',
address: '',
detailAddress: '',
photoURL: '',
},
dangerouslyAllowMutability: true, // 중간에 react, recoil,freeze 에러가 뜬걸 해결해줬던 속성인데 정확한 이유는 모르겠다.
});
const recoilPostAddress = atom({ // 주소찾기마치고 유저정보입력창 페이지로 넘어갈때 전역상태로 저장할 주소값 저장소
key: 'recoilPostAddress',
default: '',
});
마이페이지에서는 상단 간단 프로필 & 내정보수정 & 로그아웃버튼 + 중간에 공지사항 버튼 + 하단 예약현황, 사용현황을 볼 수 있게 만든 페이지이다.
로그인유무에따른 (window저장객체에 accesstoken 유무) 하단바 마이페이지 버튼을 클릭시 로그인 & 마이페이지로 분기를 나눠 페이지가 이동된다.
// BottomNav 컴포넌트 함수 내부 코드
<S.IconContainer
onClick={() => {
if (localUserType === 'admin' || sessionUserType === 'admin') { // window저장객체에 저장된 userType이 'admin' 즉 관리자 일때,
navigate(ROUTES.BUSINESS.PATH);
} else {
navigate(ROUTES.MYPAGE.PATH);
}
if (localToken === null && sessionToken === null) { // window저장객체에 Token이 없을때, 즉 로그인 되지 않은 상태 일때!
navigate(ROUTES.LOGIN.PATH);
}
}}
>
마이페이지 상단에 간단 프로필부분은 aixos요청으로 유저정보를 받아온 후 뿌려주었다.
중간에 공지사항버튼을 클릭하면 client단에서 MockList로 만들어 둔 공지사항 리스트를 맵핑하였다.
예약현황, 사용현황 리스트정보는 axios요청으로 유저의 대여현황정보 리스트를 받아와 분기를 나누고 맵핑하였다.
const Bottom = () => {
const { getUserPayment, listData } = useMyPageBottom();
useEffect(() => { // axios 요청으로 대여현황 리스트 받아오기
getUserPayment();
}, []);
// 받아온 대여현황리스트 분기 나누기
const filterReserving = listData.filter((list) => {
return list.status === 'WAITING_FOR_RESERVATION';
});
const filterUseNow = listData.filter((list) => {
return list.status === 'USE_NOW';
});
return (
<S.MyPageStatusContainer>
<S.ReservingListDiv>
<S.ReservingText>예약 현황</S.ReservingText>
{filterReserving &&
filterReserving.map((data) => (
<ReservingList data={data} key={data.id} />
))}
</S.ReservingListDiv>
<S.UseNowListDiv>
<S.UseNowText>사용 현황</S.UseNowText>
{filterUseNow &&
filterUseNow.map((data) => <UseNowList data={data} key={data.id} />)}
</S.UseNowListDiv>
</S.MyPageStatusContainer>
);
};
export default Bottom;
내정보수정은 회원가입과 마찬가지로 React-Hook-Form, Recoil, Daum-PostCode 라이브러리를 사용하여 진행하였다.