Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FE] feat: NavigationTab 컴포넌트 구현 #1037

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from

Conversation

soosoo22
Copy link
Contributor

@soosoo22 soosoo22 commented Jan 1, 2025


🚀 어떤 기능을 구현했나요 ?

  • 리뷰미 서비스에 로그인 기능이 도입 예정이므로 로그인한 사용자에게 리뷰 링크 관리작성한 리뷰 확인 페이지를 탭으로 제공하여 쉽게 전환할 수 있도록 네비게이션 탭을 구현했습니다.
    스크린샷 2025-01-01 오후 11 54 19

🔥 어떻게 해결했나요 ?

일단, 네비게이션 탭을 구현하면서 집중적으로 고려한 부분은 아래와 같습니다.

  1. 새로고침 시, 탭 유지

    • useState로 탭 상태를 관리할 경우 새로고침 시 상태가 초기화되어 기본값인 0번 인덱스 탭으로 돌아가게 됩니다. 그래서 활성화된 탭의 인덱스를 세션 스토리지에 저장하고, 새로고침 후에도 저장된 탭 인덱스를 불러와 이전에 선택한 탭을 유지하도록 했습니다.
  2. 브라우저 뒤로가기, 앞으로 가기 클릭 시, 해당 탭으로 이동

  • URL의 쿼리 파라미터를 활용해서 현재 활성화된 탭을 관리했습니다. 예를 들어, 리뷰 링크 관리 페이지에서는 쿼리 파라미터를 tab=manage-links 로 설정하고, location.search로 URL의 쿼리 값을 확인합니다. 그 후, 해당 값에 맞는 탭 인덱스를 currentIndex로 업데이트해서 브라우저의 뒤로가기나 앞으로 가기 버튼을 클릭했을 때 이전에 선택한 탭으로 자동으로 이동하도록 구현했습니다.
  1. 모바일 환경
    모바일 환경에서는 탭의 길이에 따라 각 탭이 같은 영역을 차지하도록 반응형 로직을 추가했습니다.
    스크린샷 2025-01-01 오후 11 32 50

tabInfoList는 name, path, param으로 이루어진 배열로, 탭의 이름, 경로, 쿼리 파라미터 값을 포함하고 있습니다.

  const tabInfoList = [
    { name: '리뷰 링크 관리', path: `경로 미정`, param: 'manage-links' },
    { name: '작성한 리뷰 확인', path: `경로 미정`, param: 'written-reviews' },
  ];

  return (
    // ....
        <NavigationTab tabInfoList={tabInfoList} />
    // ....
   );

📝 어떤 부분에 집중해서 리뷰해야 할까요?

탭 hover 했을 때, 배경색이 아닌 글자색을 변경하는 게 깔끔해 보이는데, 여러분들은 어떻게 생각하시나요???🤔
스크린샷 2025-01-02 오전 12 10 09
스크린샷 2025-01-02 오전 12 11 01

📚 참고 자료, 할 말

To. 리뷰미 여러분🍀

2025년 새해가 밝았네요. 새해 복 많이 받으세요~ 두 배!! 세 배!!! 네 배!!!! 더더더더 받으세요😊
늘 행복하시고, 원하는 거 다 이루시길 바랍니다. 날씨가 쌀쌀하니 감기 조심하세요~

Copy link
Contributor

@BadaHertz52 BadaHertz52 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

쑤쑤 구현한 것 잘 봤어요.

쿼리 스트링 구현

쿼리 스트링 값으로 탭을 구현한다는 아이디어 좋은 것 같아요
이거를 리뷰미에 적용하려면, 회원 페이지는 아래처럼 쿼리 스트리밍 같에 따라서 다른 children 을 넣어줘야겠네요.

          <>
            <NavigationTab
              tabInfoList={[
                { name: '리뷰 링크 관리', path: `/test`, param: 'manage-links' },
                { name: '작성한 리뷰 확인', path: `/test`, param: 'written-reviews' },
              ]}
            />
            {children}
          </>

스타일 컴포넌트 Props, 타입과 탭 하단 바 애니메이션 관련 코멘트 남겼으니 확인 부탁드려요

Comment on lines +31 to +32
const queryParams = new URLSearchParams(location.search);
const tabParam = queryParams.get('tab');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useSearchParamAndQuery 훅 이용할 수 있어요

background-color: ${({ theme }) => theme.colors.primary};
border-radius: 0.1rem;

transition: ${({ isTransitionEnabled }) => (isTransitionEnabled ? 'all 0.2s ease-in-out' : 'none')};
Copy link
Contributor

@BadaHertz52 BadaHertz52 Jan 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 활성화된 탭 하단에 있는 바의 애니메이션 효과를 위해서 바를 버튼이랑 분리했군요

저는 개인적으로 바 이동 시 애니메이션 효과가 없는게 깔끔하고 좋은 것 같아요. 바 이동 시 현재 활성화된 탭에 따라 계산을 하는데 아래 처럼 오차가 발생할 가능성도 있고요.

스크린샷 2025-01-03 오후 3 39 09

Copy link
Contributor Author

@soosoo22 soosoo22 Jan 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

목록, 모아보기 페이지 OptionSwitch 처럼 각 페이지마다 NavigationTab을 추가한다면, 애니메이션 로직이 있어도... 보이지 않아서 코드를 없앨 예정이에요:)

하단바 오차는 자식 요소의 left 값을 그대로 사용하고 있어서 생긴 오차같아요. 자식 요소의 left 값에서 부모 요소의 left 값을 빼면 오차없이 제대로 계산이 돼요.

   const currentTab = currentItemRef.current.children[currentIndex];
   const parentLeft = currentItemRef.current.getBoundingClientRect().left;
   const currentLeft = currentTab.getBoundingClientRect().left - parentLeft;

추가로! 하단바를 넣은 이유가 애니메이션을 넣고 싶어서..였기 때문에😅 애니메이션이 없어진다면 하단바도 없어지고 버튼의 border-bottom으로 할 것 같아요!

<S.NavContainer>
<S.NavList ref={currentItemRef}>
{tabInfoList.map((item, index) => (
<S.NavItem key={index} selected={currentIndex === index}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

key에 index를 사용하지 않는게 좋아서 다른 방법으로 key를 구별했으면 좋겠어요

@@ -0,0 +1,59 @@
import styled from '@emotion/styled';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

스타일 컴포넌트에 props들 에 모두 $ 표시 없네요.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

추가하겠습니다....😅

}
`;

export const CurrentNavBar = styled.div<{ width: number; left: number; isTransitionEnabled: boolean }>`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

스타일 컴포넌트 Props 가 두 개 이상일 경우 따로 타입으로 빼주세요

Copy link
Contributor

@ImxYJL ImxYJL left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

페이지 구조에서 탭의 위치, 선택된 탭에 대한 상태 관리 방식에 대한 코멘트 남겼어요~
전자는 디코에 썼는데 혹시 몰라 여기에 한번 더 남겨요!

(제가 이해한 게 맞다면) 링크 관리 페이지에서 작성한 리뷰 확인 페이지를 갖고 있는 건데, 두 페이지는 상하관계가 아니라 형제관계라 Collection 페이지와 비슷한 구조로 가야할 것 같아요~

쑤쑤도 새해 복 많이 받아요 😆😆

const NavigationTab = ({ tabInfoList }: NavigationTabProps) => {
const activeTab = sessionStorage.getItem('activeTab');

const [currentIndex, setCurrentIndex] = useState(Number(activeTab) || 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

탭에서 선택 상태를 관리하고, 또 그 관리 방법이 useState인 이유가 있는지 궁금해요.
(아마 애니메이션 추가 + 페이지를 각자 다른 사람이 만들고, 탭 위치에 대해서 제대로 얘기한 적이 없어서 해석에 차이가 나는 것 같아요!)

현재 OptionSwtich의 구현을 보면 현재 선택된 옵션 상태가 컴포넌트 외부(훅)로 분리되어 있고, useState가 아닌 단순 js 변수를 사용해요. 그리고 선택된 요소를 url을 통해 판단하고 있어요.

const { pathname } = useLocation();
const { param: reviewRequestCode } = useSearchParamAndQuery({
    paramKey: 'reviewRequestCode',
  });

// 여기서는 includes만 1번 사용하고 있어서 확장성이 조금 떨어지지만 
// 기존 breadcrumb과 유사한 방식으로 변경하면 괜찮을 것 같아요
const isReviewCollection = pathname.includes(ROUTE.reviewCollection);

그리고 이 상태를 이용해 선택 상태를 제어하기 때문에 새로고침이 일어나도 기존 페이지를 불러올 수 있어요.

const reviewDisplayLayoutOptions: OptionSwitchOption[] = [
    {
      label: '목록보기',
      isChecked: !isReviewCollection,
      handleOptionClick: navigateReviewListPage,
    },
    ...

뒤로가기 동작 또한 방문했던 url 기반이기 때문에 자연스럽게 동작합니다.

url 기반 상태관리와 useState 기반 관리 모두 각자 장단점이 있지만, 지금 리뷰미에서는 url을 적극적으로 이용하고 있고(reviewRequestCode를 url에서 가져와 사용하는 등) 이 방식을 사용해도 중요한 정보가 url에 노출되지 않기 때문에, 또 탭 자체도 url과 연관이 깊기 때문에 url 방식이 더 낫다는 의견입니다~!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(제가 이해한 게 맞다면) 링크 관리 페이지에서 작성한 리뷰 확인 페이지를 갖고 있는 건데, 두 페이지는 상하관계가 아니라 형제관계라 Collection 페이지와 비슷한 구조로 가야할 것 같아요~

제가 디코에서 말을 잘못 전달한 것 같아요..😭 디코에서는 제가 링크 관리 페이지에 네비게이션 탭을 두고, 작성한 리뷰 확인 탭을 클릭하면 해당 페이지 안에서 작성한 리뷰 확인 페이지를 불러오는 방식이라고 설명해서 상하 구조로 오해가 생긴 것 같아요. 사실 제가 생각했던 방식은 링크 관리 페이지작성한 리뷰 확인 페이지를 포함하는 부모 페이지를 만들어서, 네비게이션 탭은 부모 페이지에만 두고 탭을 클릭하면 네비게이션 탭은 그대로 유지되면서, 탭 아래의 컨텐츠가 전환되는 방식을 생각했어요. 그래서 부모 페이지를 만들어 하나의 탭만 넣을지, 두 개의 페이지에 각각 탭을 넣을지? 고민했었어요🤔

부모 페이지(/my-page)를 만들어서 리뷰 링크 관리 탭을 클릭하면 /my-page?tab=manage-links로 이동하는 방식?

탭에서 선택 상태를 관리하고, 또 그 관리 방법이 useState인 이유가 있는지 궁금해요.

제가 원했던 탭 방식이 각 탭을 클릭하면 하단바가 애니메이션을 통해 부드럽게 이동하는 것이였어요. 그래서 하단바가 선택된 탭 상태에 따라 width와 left를 바로 계산해서 동적으로 변경해 줘야 하는데 새로고침이나 탭 간 이동 시, 하단바가 깜빡이거나 늦게 나타나는 문제가 있어서 useState와 세션 스토리지를 결합해서 사용했어요..😅

return (
<S.NavContainer>
<S.NavList ref={currentItemRef}>
{tabInfoList.map((item, index) => (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

프로필 탭 리뷰에서도 남겼지만, 합성 컴포넌트 방식을 사용해서 map 대신 태그를 통해 어떤 요소들이 렌더링되는지 바로 알 수 있도록 만드는 방법은 어떨까요?? 링크

Copy link
Contributor

@chysis chysis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

쿼리 파라미터로 구현한 코드 잘 봤어요🔥

탭 hover 했을 때, 배경색이 아닌 글자색을 변경하는 게 깔끔해 보이는데, 여러분들은 어떻게 생각하시나요???🤔

실제로 구현된 모습을 비교해 보니 글자색만 변경해도 괜찮을 것 같네요!


쿼리 파라미터를 사용한 만큼, 세션 스토리지를 사용하지 않고도 활성화된 탭 상태를 유지할 수 있지 않을까요?

간단하게 코멘트 남겼어요. 확인 부탁해요!

Comment on lines +51 to +55
const handleTabClick = (path: string, index: number) => {
setCurrentIndex(index);
setIsTransitionEnabled(true);
navigate(`${path}?tab=${tabInfoList[index].param}`);
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 활성화된 탭을 클릭할 경우 불필요한 상태 업데이트가 발생할 것 같아요. index와 currentIndex가 다른 경우에만 업데이트하는 것은 어떨까요?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: No status
4 participants