지난 시간에 이어서 상단 유저 프로필 모달창에 대해서 다루고자 한다.
전체 코드를 리뷰하여, 왜 이렇게 코드를 작성하였는지, 또한 유지관리 측면이라는 점에서 어떤 방식이 더 유리할지 생각해보고자 한다.
보기 편한게 더 좋은 코드가 아닐까?
로그인이 되기 전 모달창과 로그인 이후 모달창은 각각 구성되는 선택 옵션에서 차이가 있기에, 따로 컴포넌트로 관리하였다.
우선 로그인이 되기 전 모달창을 살펴보자면
const ProfileContainer = ({
setProfileModal,
switchModal,
}) => {
return (
<>
<ModalOverlayInUserInfo onClick={() => setProfileModal(false)} />
<ModalProfile onClick={e => e.stopPropagation()}>
<TopContainer>
<UserInfoMenu onClick={switchModal}>
<InfoMenuLogin>로그인</InfoMenuLogin>
</UserInfoMenu>
<UserInfoMenu>
<InfoMenuText>회원가입</InfoMenuText>
</UserInfoMenu>
</TopContainer>
<DivLine />
<BottomContainer>
<UserInfoMenu>
<InfoMenuText>숙소 호스트되기</InfoMenuText>
</UserInfoMenu>
<UserInfoMenu>
<InfoMenuText>체험 호스팅하기</InfoMenuText>
</UserInfoMenu>
<UserInfoMenu>
<InfoMenuText>도움말</InfoMenuText>
</UserInfoMenu>
</BottomContainer>
</ModalProfile>
</>
);
};
export default ProfileContainer;
각각의 옵션들을 하나하나 기입하였다. 수가 5개정도밖에 되지 않기 때문에, 딱히 반복을 써서 처리하는 것이 의미가 있을 까 싶었다. 또한 누가 보더라도 쉽게 내용을 파악할 수 있지 않을까 싶기도 하였다.
로그인에 들어간 함수 및 overlay 등 지난 작성글에서 설명한 부분은 패스하겠다.
다음에는 로그인 이 된 후 모달창 코드이며 아래와 같다.
const ProfileLoginContainer = ({
setProfileModal,
setIsToken,
switchModal,
}) => {
const navigate = useNavigate();
const deleteToken = () => {
localStorage.removeItem('Token');
localStorage.removeItem('key');
alert('로그아웃 되었습니다.');
setIsToken(false);
setProfileModal(false);
};
const moveToResList = () => {
navigate('/resList');
setProfileModal(false);
};
return (
<>
<ModalOverlayInUserInfo onClick={() => setProfileModal(false)} />
<ModalProfile onClick={e => e.stopPropagation()}>
{CONTAINER.map((List, idx) => {
return (
<ModalInnerContainer key={idx}>
{idx === 0 ? (
<TopContainer>
{Object.values(List).map((text, j) => {
return (
<UserInfoMenu
key={j}
onClick={
j === 2 || j === 3
? j === 3
? switchModal
: moveToResList
: null
}
>
<InfoMenuLogin>{text}</InfoMenuLogin>
</UserInfoMenu>
);
})}
</TopContainer>
) : (
<BottomContainer>
{Object.values(List).map((text, j) => {
return (
<UserInfoMenu
key={j}
onClick={idx === 2 && j === 1 ? deleteToken : null}
>
<InfoMenuText>{text}</InfoMenuText>
</UserInfoMenu>
);
})}
</BottomContainer>
)}
{idx === 2 ? null : <DivLine />}
</ModalInnerContainer>
);
})}
</ModalProfile>
</>
);
};
export default ProfileLoginContainer;
const CONTAINER = [
{
1: '메세지',
2: '알림',
3: '여행',
4: '회원정보수정',
},
{
1: '숙소 호스트되기',
2: '체험 호스팅하기',
3: '호스트 추천하기',
4: '계정',
},
{
1: '도움말',
2: '로그아웃',
},
];
코드가 조금 길어졌는데, 밑에 CONTAINER 상수를 보면 알 수 있겠지만, 각각의 선택 옵션값들을 상수화 시킨 후 반복문을 돌려서 렌더링 한 코드다.
조금 나누어서 다시 설명을 해보자면
const ProfileLoginContainer = ({
setProfileModal,
setIsToken,
switchModal,
}) => {
// 생략
return (
<>
<ModalOverlayInUserInfo onClick={() => setProfileModal(false)} />
<ModalProfile onClick={e => e.stopPropagation()}>
{CONTAINER.map((List, idx) => {
return (
<ModalInnerContainer key={idx}>
{idx === 0 ? (
<TopContainer>
// 생략
</TopContainer>
) : (
<BottomContainer>
// 생략
</BottomContainer>
)}
{idx === 2 ? null : <DivLine />}
</ModalInnerContainer>
);
})}
</ModalProfile>
</>
);
};
export default ProfileLoginContainer;
상수 CONTAINER 에서 배열 안 객체들을 이용하여 map 을 돌릴것인데, CSS 적용으로 인해서 최상단 부분과, 아래부분을 container 로 구별지어서 적용하였기에 반복을 돌릴때도, 이를 구별해주고자 idx 의 조건에 따라 <TopContainer> , <BottomContainer> 를 구별하여 사용하도록 하였다.
idx 가 0 일 경우만 <TopContainer> 를 적용하고, 나머지는 모두 <BottomContainer> 를 적용하도록 조건을 위 코드처럼 삼항식을 달아주었다.
밑에 보면 {idx === 2 ? null : <DivLine />} 이 있는데, 이는 모달창에서 보이는 구별선이라고 생각하면 된다. 즉, idx 가 2 라는 말은 마지막 반복부분이기 때문에 구별선을 그 밑에 추가하지 않기 위해서 조건을 걸어주었따. 쉽게 생각해 구별선은 컨텐츠와 컨텐츠 사이에 있어야 하기 때문에, 마지막 컨텐츠 옵션이 반복하여 화면에 랜더링 되어있다면 그 이후는 구별선이 필요 없기 때문이다.
이렇게 기본적인 틀을 잡아 둔 다음,
const ProfileLoginContainer = ({
setProfileModal,
setIsToken,
switchModal,
}) => {
// 생략
// 생략
const moveToResList = () => {
navigate('/resList');
setProfileModal(false);
};
return (
<>
<ModalOverlayInUserInfo onClick={() => setProfileModal(false)} />
<ModalProfile onClick={e => e.stopPropagation()}>
{CONTAINER.map((List, idx) => {
return (
<ModalInnerContainer key={idx}>
{idx === 0 ? (
<TopContainer>
{Object.values(List).map((text, j) => {
return (
<UserInfoMenu
key={j}
onClick={
j === 2 || j === 3
? j === 3
? switchModal
: moveToResList
: null
}
>
<InfoMenuLogin>{text}</InfoMenuLogin>
</UserInfoMenu>
);
})}
</TopContainer>
) : (
<BottomContainer>
// 생략
</ModalProfile>
</>
);
};
export default ProfileLoginContainer;
const CONTAINER = [
{
1: '메세지',
2: '알림',
3: '여행',
4: '회원정보수정',
},
// 생략
];
첫번 째 <TopContainer> 에서 반복을 돌릴것인데, 각각 '메세지','알림','여행','회원정보수정' 이 들어가게 될것이다.
Object.values(List) 를 통해서 (여기서 List 는 더 위 CONTAINER 에서의 각각의 배열값) 메세지, 알림 등 value 를 가져온다. 이후 중첩 map 을 통해서 작성을 해줄 것인데, 이 중 '여행' 과 '회원정보수정' 에는 각각 함수를 적용시켜주어야 한다.
'여행' 은 실제 숙소예약목록 페이지로 이동시키는 함수, '회원정보수정' 은 로그인 이후 회원정보를 추가 입력시 나타나게 될 페이지로 이동시켜야 한다. 반복문을 돌리면서 조건에 맞게 함수를 집어넣어주자.
두번째 map 의 인덱스 j 값에 따라서 조건을 걸어줄 것인데, 당시에는 중첩 삼항조건식이 가장 깔끔하게 풀릴것이라 판단했다. j 가 2 나 3일 때가 첫번재 조건, j가 3일 때가 두번째 조건으로서 두번째 조건까지 만족시킨다면 함수 'switchModal' 을 넣어주고, 첫번째만 만족한다면... 즉 j 가 2라면 'moveToResList' 함수를 넣어준다.
이 결과 '여행' 에는 moveToResList 가 '회원정보수정' 에는 'switchModal' 이 들어가게 된다.
여기서 갑자기 왜 switchModal 이 나오냐 궁금할 수 있는데, 추후 마지막으로 살펴볼 Nav 컴포넌트에서 나오겠지만, 일단 먼저 설명을 해보자면
이전에 switchModal 의 경우 기존 옵션 모달창을 닫고, 로그인 모달창을 실행시키는 함수로 설명했었다. 이때 로그인 모달창은 유저의 Token 값이 브라우저에 존재하지 않을 때 나타나야 하는 모달창이고, 만일 로그인이 되어있다면(즉, Token 값이 저장되어있다면) 어차피 다른창이 나타나야 하기도 하고, switchModal 함수 및 다루는 state 를 재활용 하기 위해서
Nav 에 Token 값 여부에 따라 나타나는 모달창을 나누어 놨다. (로그인 모달창과 회원정보수정 모달창을 token 에 따라 구별하겠다는 의미)
따라서 여기서 switchModal 실행 시 나타나는 모달창은 로그인창이 아닌 회원정보수정 모달창이 나타나게 된다.
(이 설정이 Nav 컴포넌트 안에 있다.)
참고로 moveToResList 는 navigate 로 숙소예약페이지로 이동시켜주고 모달창을 종료시킨다.
(지금 생각해보면 모달창을 종료시키는 변화를 계속해서 줘야 하는건가 싶기도 하고, 페이지가 새로 랜더링될때 false 로 고정시키면 되는 거 아닌가 싶기도 하다...만 여튼)
이렇게 상단부분이 완료가 됬다면 이제 나머지 하단부분도 역시나 map 을 돌리면
const ProfileLoginContainer = ({
setProfileModal,
setIsToken,
switchModal,
}) => {
const deleteToken = () => {
localStorage.removeItem('Token');
localStorage.removeItem('key');
alert('로그아웃 되었습니다.');
setIsToken(false);
setProfileModal(false);
};
return (
<>
<ModalOverlayInUserInfo onClick={() => setProfileModal(false)} />
<ModalProfile onClick={e => e.stopPropagation()}>
{CONTAINER.map((List, idx) => {
return (
<ModalInnerContainer key={idx}>
{idx === 0 ? (
<TopContainer>
// 생략
</TopContainer>
) : (
<BottomContainer>
{Object.values(List).map((text, j) => {
return (
<UserInfoMenu
key={j}
onClick={j === 1 ? deleteToken : null}
>
<InfoMenuText>{text}</InfoMenuText>
</UserInfoMenu>
);
})}
</BottomContainer>
)}
{idx === 2 ? null : <DivLine />}
</ModalInnerContainer>
);
})}
</ModalProfile>
</>
);
};
export default ProfileLoginContainer;
const CONTAINER = [
// 생략
{
1: '숙소 호스트되기',
2: '체험 호스팅하기',
3: '호스트 추천하기',
4: '계정',
},
{
1: '도움말',
2: '로그아웃',
},
];
같은 원리로 특정 부분에만 함수를 넣어줘야 하는데, 로그아웃 부분에만 함수를 넣어줄 예정이다.
로그아웃 함수는 실제로 토큰을 지워주면서 동시에 isToken 을 false 로 변경시켜 바로 랜더링 될 수 있도록 해준다.
2가지의 List 중에서 마지막 3번째 List 에 적용이 되어야 하기에, idx === 2 여야 하고, 그 다음 중첩 반복문에서의 인덱스 값 j === 1 일 경우가 바로 '로그아웃' 이기에, 이번에는 && 를 활용하여 조건을 위처럼 넣어주었다. 그 왜에는 그냥 반복을 돌려 주면 된다.
적다보니깐 뭔가 이렇게 길어질지는 몰랐다. 그냥 반복문이라 쉽게 설명하고 끝날 줄 알았는데, 중첩된 반복문을 설명하다 보니 길어진 느낌이 든다. 거기다가 특정 선택옵션에 맞는 함수를 넣어줘야 하기에 더더욱 그런느낌이 든다.
그러다보니 문득 그런 생각이 든다. 꼭 이렇게 반복문을 돌려서 작업했어야 할까. 그냥 다 나열해서 작업하는게 함수넣기도 편하고 오히려 좋지 않을까... 당시 작업할 때도 그렇고 지금도 그런 생각이 들곤 한다. 어떤 방식이 더 가독성을 높히는 방법일까?
가독성이란 당장 내 눈앞에 편하다고 좋은 것은 아닐 것이다.
맨 앞에 로그인 창 모달창을 보게 되면, 반복문을 쓰지 않고 그대로 작성하였다. 실제 반복을 돌릴만큼의 수도 아닌것 같았고, 그냥 한눈에 구조가 들어오니 훨씬 더 간단하다고 생각하였다. 함수 넣기도 편하고 지금 생각해도 쉽게 파악되니,(지금은 프로젝트가 끝난지 어연 1달이 넘었다...) 이게 더 가독성이 좋은것 아닌가 싶은 생각이 들기도 하였다.
반면 두번째 반복이 들어간 모달창을 보게 되면, 솔직하게 말해서 지금 블로그를 적기 전에 약간 해석하는 시간이 필요했다. 내가 작성한 코드인데도 말이다. 그렇다면 남들이 보면 더 해석이 안되는것 아닐까 하는 우려도 생겼다.
해석을 하기 힘들다면 가독성이 안좋다고 할 수 있지 않을까? 그렇다면 그냥 반복문으로 돌리는게 아니라 나열해서 작성하는게 더 파악하기 편하지 않았을까?
앞으로도 코드를 작성할때마다 드는 고민이겠지만, '가독성' 이라는 부분은 여전히 나에게 난제로 자리잡을 것 같다. 다만, 적어도 지금같은 예제에서는 가독성이라는 부분에 대해 조금 더 다르게 생각해볼 필요가 있다..
코드의 가독성은 유지보수에 있어서 용이한 코드에서 나타난다
예전에 가독성에 대해 고민을 하다 학원 멘토님한테 들은 한마디인데, 이후 가독성이라는 단어에 대해 내 선입견이 작용했다는 것을 느낀적이 있었다. 너무 보이는 것에만 집중한 것이 나의 선입견! 이후 다시 곰곰히 생각을 해봤다...
그저 내 눈앞에 편한 코드라고 해서 가독성이 좋다고 할 수 없는 이유는 크게 2가지로 생각이 든다.
- 내가 보기 편한 이유가 깔끔해서가 아니라 내가 작성해서 그런것은 아닌가? 남들이 봐도 잘 이해하는 코드인가?
- 코드를 구성하는 컨텐츠는 언제나 변화할 수 있고, 컨텐츠가 변화할 때 유지보수가 쉬운 코드인가?
2가지 조건으로 다시 첫 로그인 모달창을 살펴보게 되면,
분명 내가 봤을때는 이 모달창이 바로 로그인 모달창이라는 것을 파악할 수 있지만, 만일 처음 보는 사람이 이 코드를 보고 로그인 모달창이라고 바로 파악할 수 있다고 확신할 수 있는가? 로그인 모달창이라고 파악할 수 있는 점은 컴포넌트 명칭이 있겠지만, 코드를 보고 파악한다면 결국은 코드안에 들어가는 text, 즉 컨텐츠 내용('로그인','회원가입' 등등) 일텐데, 그렇다면 오히려 타인의 경우 파악이 더 쉬운것은 반복문을 사용한 로그인 이후 모달창 아닐까? 왜냐하면 컨텐츠가 바로 상수 객체로 깔끔하게 정리되어 있기 때문이니깐.
그리고 만일 컨텐츠의 내용이 달리지거나 추가된다면, 컨텐츠를 구성하는 태그들까지 같이 추가해야하는 위 로그인모달창과 달리 처음부터 반복문을 사용하여 랜더링 하고 있었기에, 그냥 상수부분에 데이터만 추가해주면 알아서 생성이 된다. 수정 역시 상수 객체에서 수정해주면 되기 때문에 훨씬 유지보수가 간단해진다.
이렇게 생각해보면 가독성이 좋다는 것은 복합적인 의미를 가지고 있으며, 내 주관적인 판단을 조금 배제해야 하는 느낌을 받는다. 비즈니스를 구성하는 상황은 언제나 변화하고 그에 따라 수시로 변화하게 되는 것이 코드일테니, 항상 변화에 유의를 하며 코드를 작성해야할 것 같고, 왜 사람들이 변수 이름을 정하는것을 어려워 하는지 이제야 이해가 간다.. (남들이 봐서 알아봐야하니깐)
이전 작성글에 같이 작성하려했던 내용인데, 나누길 잘 한 것 같다.
다음에는 위 상단 검색바를 활성화 시킬때와 아닐때의 Nav 에 대해서 살펴보겠다.
'Programing > React' 카테고리의 다른 글
2차 프로젝트 - 에어비엔비 Nav(7) (0) | 2022.09.17 |
---|---|
2차 프로젝트 - 에어비엔비 2개의 Nav로 구성하기(6) (0) | 2022.09.14 |
2차 프로젝트 - 에어비엔비 우측 상단 프로필 버튼(4) (0) | 2022.09.03 |
2차 프로젝트 - 에어비엔비 상단 검색창 - Location, Calender, User(3) (2) | 2022.08.31 |
2차 프로젝트 - 에어비엔비 상단 검색창 - Search(2) (0) | 2022.08.30 |