● 2일차 작업 분배
❍ Comment.tsx(준석)
- query Invalidation, query mutation hook 으로 처리하기 (Comment.tsx)
- FeedComment, Comment 로직 통일하기 (분리의 필요성을 못 느끼겠어요)
❍ Header.tsx(다은)
- Header query → hook으로 분리 (myPage를 키로 하는 query호출이 여러 곳에서 이루어집니다. )
- Header Container 컴포넌트로 분기 처리 하기 (같은 JSX 코드가 반복)
● Header.tsx query hook처리하기
먼저 로직분리를 하려고 Header.tsx를 봤더니 떡하니 쿼리가 있었읍니다.. 저희는 모든 쿼리와 뮤테이션을 hook으로 만들어서 처리하는게 좋지 않겠냐는 이야기가 있었기 때문에(중복되는 것들만).. Header.tsx에 쓰이는 query는 중복이 심한 쿼리였기때문에 hook으로 먼저 처리를 하기로 했읍니다.
아래는 Header.tsx에서 쓰이던 쿼리입니다.
const { data, isSuccess } = useQuery({
queryKey: ['myPage'],
queryFn: () =>
getServerDataWithJwt(`${SERVER_URL}/members`, accessToken as string),
enabled: !!accessToken,
});
아래는 Mypage.tsx에서 쓰이던 쿼리입니다.
const {
data,
isLoading,
isError: isUserError,
} = useQuery<UserInfo>({
queryKey: ['myPage'],
queryFn: () =>
getServerDataWithJwt(`${SERVER_URL}/members`, accessToken as string),
onSuccess(data) {
setUserProfileImage(data.memberInfo.imageURL);
},
});
묘~하게 코드중복이 있습니다. 이외에도 쿼리키가 'myPage'인데 중복되는 쿼리들이 다른 파일에도 적어도 5개..가 있었습니다. 사실은 10개정도 되는것같아보였읍니..읍읍..
그래서 useMypageQuery라는 hook을 만들어 처리하였습니다.
src > hook > query > useMypageQuery.tsx
import { useQuery } from '@tanstack/react-query';
import { getServerDataWithJwt } from '@/api/queryfn';
import { UserInfo } from '@/types/userType';
interface QueryProp {
url: string;
accessToken: string | null;
successFn?: React.Dispatch<React.SetStateAction<string>>;
}
export function useMypageQuery({ url, accessToken, successFn }: QueryProp) {
const { data, isLoading, isSuccess, isError } = useQuery<UserInfo>({
queryKey: ['myPage'],
queryFn: () => getServerDataWithJwt(url, accessToken as string),
onSuccess: data => {
if (successFn) successFn(data.memberInfo.imageURL);
},
enabled: !!accessToken,
});
return { data, isLoading, isSuccess, isError };
}
어차피 불러오는 모든 쿼리에 accessToken을 전달해주어야했기때문에, enabled에 넣어주는것은 상관없었지만, onSuccess 함수는 필요한 쿼리도 있고 필요가 없는 쿼리가 있어서 어떻게 처리해야하지? 란 생각이 있었습니다.
useQuery 내부에서 if문으로 처리할 수 있는 것도 아니고.. 생각해보니 함수안에서 If를 쓰는구나 ^^; 라는 생각과함께 한큐에 해결할 수 있었습니다. - 해결 완 -
query를 hook으로 처리하면서 실제로 이게 코드 최소화에 영향이 있을까? 란 생각을 했는데, query를 불러오는 import뿐만 아니라 interface를 Import해오는 코드도 줄이면서 한두줄씩이라도 줄어드는 효과를 보았습니다..
그래.. 모든 query hook처리해야겠구나..허허
● Header.tsx 안에 있는 Nav탭 컴포넌트로 분리하기
저희 팀의 코드에서 가장 큰 문제는 return 문 내부에 if 및 삼항연사자를 통해서 return 문이 나뉘는게 가독성이 매우 떨어진다는 점이었습니다.. 그래서 준석님과도 지속적으로 이야기한게, 이런 부분을 컴포넌트로 빼서 관리하는게 맞다. 중복되는 로직이 삼항연산자로 나뉘면서 많았기때문에 Header.tsx도 Nav탭 컴포넌트로 분리해 해결하기로 했습니다.
아래는 저희 Header.tsx 부분에서 return 문입니다.
if (accessToken && isSuccess) {
return (
<>
{isOpen && (
<Popup
title={'산책게시물을 보려면 펫을 등록해주세요!'}
handler={[
() => {
setIsOpen(false);
navigate(Path.MyPage);
},
]}
isgreen={['true']}
btnsize={['md']}
buttontext={['펫 등록하러가기']}
countbtn={1}
popupcontrol={() => setIsOpen(false)}
/>
)}
<HeaderContainer>
<div>
<Link to={Path.Home}>
<Logo className="w-52 h-8" />
</Link>
</div>
<nav>
<NavList>
<NavItem active={matchHome !== null ? 'false' : 'true'}>
<Link to={Path.Home}>홈</Link>
</NavItem>
{data?.animalParents && (
<NavItem active={matchWalkmate !== null ? 'false' : 'true'}>
<Link to={Path.WalkMate}>산책</Link>
</NavItem>
)}
{!data?.animalParents && (
<NavItem
active={matchWalkmate !== null ? 'false' : 'true'}
className="cursor-pointer"
onClick={() => setIsOpen(true)}>
산책
</NavItem>
)}
<NavItem active={matchPost !== null ? 'false' : 'true'}>
<Link to={Path.FeedPosting}>포스트</Link>
</NavItem>
<li>
<Link to={Path.MyPage}>
<Profile
size="md"
url={data.memberInfo.imageURL}
isgreen={matchMypage !== null ? 'true' : 'false'}
/>
</Link>
</li>
<li>
<Button
size="sm"
text="로그아웃"
isgreen="true"
handler={handleClick}
/>
</li>
</NavList>
</nav>
</HeaderContainer>
</>
);
} else {
return (
<>
{isOpen && (
<Popup
title={'로그인을 해주세요.'}
handler={[
() => {
setIsOpen(false);
navigate(Path.Login);
},
]}
isgreen={['true']}
btnsize={['md']}
buttontext={['로그인하러가기']}
countbtn={1}
popupcontrol={() => setIsOpen(false)}
/>
)}
<HeaderContainer>
<div>
<Link to={Path.Home}>
<Logo className="w-52 h-8" />
</Link>
</div>
<nav>
<NavList>
<NavItem active={matchHome !== null ? 'false' : 'true'}>
<Link to={Path.Home}>홈</Link>
</NavItem>
<NavItem
active="true"
className="cursor-pointer"
onClick={() => setIsOpen(true)}>
산책
</NavItem>
<NavItem
active="true"
className="cursor-pointer"
onClick={() => setIsOpen(true)}>
포스트
</NavItem>
<li>
<Button
size="sm"
text="로그인"
isgreen="true"
handler={handleClick}
/>
</li>
</NavList>
</nav>
</HeaderContainer>
</>
);
PopUp 컴포넌트 제외하더라도 가독성이 매우떨어지는 ㅠ_ㅜ 그래서 NavItem 컴포넌트로 분리하였습니다.
NavItem 컴포넌트에서, switch 구문에 따라 헤더에서 쓰일 탭을 리턴하게 했습니다.
NavItem.tsx
switch (path) {
case 'Home':
return (
<SC.NavItemContainer active={matchHome !== null ? 'false' : 'true'}>
<Link to={Path.Home}>홈</Link>
</SC.NavItemContainer>
);
case 'Walk':
return (
<SC.NavItemContainer active={matchWalkmate !== null ? 'false' : 'true'}>
<Link to={Path.WalkMate}>산책</Link>
</SC.NavItemContainer>
);
case 'NoWalk':
return (
<SC.NavItemContainer
active={matchWalkmate !== null ? 'false' : 'true'}
className="cursor-pointer"
onClick={handler}>
산책
</SC.NavItemContainer>
);
case 'Post':
return (
<SC.NavItemContainer active={matchPost !== null ? 'false' : 'true'}>
<Link to={Path.FeedPosting}>포스트</Link>
</SC.NavItemContainer>
);
case 'NoPost':
return (
<SC.NavItemContainer
active="true"
className="cursor-pointer"
onClick={handler}>
포스트
</SC.NavItemContainer>
);
}
위와 같이 컴포넌트로 분리후, Header.tsx에서의 return문입니다.
if (accessToken && isSuccess) {
return (
<>
{isOpen && (
<Popup
title={'산책게시물을 보려면 펫을 등록해주세요!'}
handler={[
() => {
setIsOpen(false);
navigate(Path.MyPage);
},
]}
isgreen={['true']}
btnsize={['md']}
buttontext={['펫 등록하러가기']}
countbtn={1}
popupcontrol={() => setIsOpen(false)}
/>
)}
<HeaderContainer>
<div>
<Link to={Path.Home}>
<Logo className="w-52 h-8" />
</Link>
</div>
<nav>
<NavList>
<NavItem path="Home" />
{data?.animalParents && <NavItem path="Walk" />}
{!data?.animalParents && (
<NavItem path="NoWalk" handler={() => setIsOpen(true)} />
)}
<NavItem path="Post" />
<li>
<Link to={Path.MyPage}>
<Profile
size="md"
url={data && data.memberInfo.imageURL}
isgreen={matchMypage !== null ? 'true' : 'false'}
/>
</Link>
</li>
<li>
<Button
size="sm"
text="로그아웃"
isgreen="true"
handler={handleClick}
/>
</li>
</NavList>
</nav>
</HeaderContainer>
</>
);
} else {
return (
<>
{isOpen && (
<Popup
title={'로그인을 해주세요.'}
handler={[
() => {
setIsOpen(false);
navigate(Path.Login);
},
]}
isgreen={['true']}
btnsize={['md']}
buttontext={['로그인하러가기']}
countbtn={1}
popupcontrol={() => setIsOpen(false)}
/>
)}
<HeaderContainer>
<div>
<Link to={Path.Home}>
<Logo className="w-52 h-8" />
</Link>
</div>
<nav>
<NavList>
<NavItem path="Home" />
<NavItem path="NoWalk" />
<NavItem path="NoPost" handler={() => setIsOpen(true)} />
<li>
<Button
size="sm"
text="로그인"
isgreen="true"
handler={handleClick}
/>
</li>
</NavList>
</nav>
</HeaderContainer>
</>
);
총 131줄인데.. 그래도 이정도면 ㅠ_ㅠ 많이 줄였다고 생각합니다..