[리팩토링] Tab 컴포넌트

yoon Y·2022년 3월 9일
0

[2nd_Project] WaffleCard

목록 보기
10/15

기존 구현 방법

Tab을 이동할 때마다 매번 url을 변경하고, pathName을 가져와서
해당 tab에 맞는 데이터를 불러와 카드리스트를 렌더링하는 방법.

문제점

하지만 채팅 모달과 카드 생성 모달도 url을 이용해야했는데, 모달이기 때문에
뒤에 탭과 카드리스트가 보여서 모달을 띄울 때마다 카드리스트도 신경써야했다.
그렇기에 모달이 띄워질 때 url에 tab이름이 포함되어야했는데, tab이동 시의 여러 경우를 처리하느라 코드 또한 복잡해졌다.

// HomePage.jsx
 ...
  useEffect(() => {
    const init = async () => {
      const newUSerInfo = await getUserInfoByToken();
      setUserInfo(newUSerInfo);
      const currentUrlArr = window.location.pathname.split('/');
      setCurrentParam(() => {
        return currentUrlArr[currentUrlArr.length - 1];
      });

      if (currentUrlArr.includes('today') || currentUrlArr[1] === '') {
        getTodayCardList();
      } else if (currentUrlArr.includes('my')) {
        getMyCardList(userInfo?.id);
      } else if (currentUrlArr.includes('like')) {
        getBookmarkCardList();
      }
    };
    setIsLoading(true);
    init();
    setIsLoading(false);
    // eslint-disable-next-line
  }, [currentParam]);

  const handleTabClick = () => {
    setCurrentParam(() => {
      const currentUrlArr = window.location.pathname.split('/');
      return currentUrlArr[currentUrlArr.length - 1];
    });
  };
 ...

해결책

Tab이동 시 카드 데이터를 불러오는 것을 url에 의존하는 것이 아닌, 상태에 의존하도록 변경했다.
Tab이동할 때 url은 루트 경로로 고정된다.

TabItem누를 시 name속성으로 지정한 텍스트(total, like, my)가 HomePage의 cardListName상태로 전달되고, cardListName에 어떤 키워드가 담겼는지에 따라 그 tab에 해당하는 카드 리스트 데이터를 불러온다.

// HomePage.tsx
  const [tabValue, setTabValue] = useState('total');

  ...
  
  useEffect(() => {
    const initWaffleCardsByType = async () => {
      setIsLoading(true);
      await setWaffleCardsByType(tabValue, { cached: true });
      setIsLoading(false);
    };

    initWaffleCardsByType();
  }, [setWaffleCardsByType, tabValue]);
  // tabValue 상태를 의존으로 api를 실행시킨다

  ...
  
  return (
    <Container>
      <Tab onClick={setTabValue} currentActiveTabItem={tabValue} />
      // Tab컴포넌트에 setTabValue함수를 전달해 TebItem이 클릭될 때 내부에서 실행되게 한다
      ...
    </Container>
  );

그 외 개선점

탭에 대한 정보를 상수로 저장해서 사용하도록 리팩토링했다
탭포인터 이동을 해야하기때문에 인덱스가 꼭 필요했고
상수를 객체들이 들어있는 배열의형태로 만들어서 구현했다
프로퍼티에 대한 인덱스를 구할땐 findindex함수를 사용했다


// constants/index.js
export const TAB_MENU = [
  { title: '오늘의 카드', name: 'total' },
  { title: '나의 카드', name: 'my' },
  { title: '관심 카드', name: 'like' },
];

// Tab.jsx
return (
    <TabItemContainer {...props}>
      <TabItem
        title={TAB_MENU[0].title}
        name={TAB_MENU[0].name}
        activeItem={activeItem}
        onClick={handleClickTabItem}></TabItem>
      <TabItem
        title={TAB_MENU[1].title}
        name={TAB_MENU[1].name}
        activeItem={activeItem}
        onClick={handleClickTabItem}></TabItem>
      <TabItem
        title={TAB_MENU[2].title}
        name={TAB_MENU[2].name}
        activeItem={activeItem}
        onClick={handleClickTabItem}></TabItem>
      <TabItemPointer currentActive={activeItem} {...props}></TabItemPointer>
    </TabItemContainer>
  );

const TabItemPointer = styled.div`
  ...
  transform: ${({ currentActive }) =>
    currentActive &&
    `translate(${
      TAB_MENU.findIndex(obj => obj.name === currentActive) * 100
    }%, 0)`};
`;

피드백
인덱스 정보가 유의미한 정보는 아니기 때문에 (뷰 스타일에 관한 것)
배열보다는 객체 형태로 바꿔서 키를 기준으로 인덱스를 구하는게 좋을 것 같고, TabItem컴포넌트 하드 코딩하는 것보다 상수 객체로 map을 돌려서 키와 값을 뽑아내 TabItem컴포넌트 렌더링하는 방법이 더 좋을 거라는 피드백을 들었다

  • 객체로 map을 사용하는 방법
  return (
    <TabItemContainer {...props}>
      {Object.entries(TAB_MENU).map(([key, value]) => (
        <TabItem
          title={value}
          name={key}
          activeItem={activeItem}
          onClick={handleClickTabItem}
        />
      ))}
      <TabItemPointer currentActive={activeItem} {...props}></TabItemPointer>
    </TabItemContainer>
  );

  • 객체의 특정 key의 index를 찾는 방법
const TabItemPointer = styled.div`
  ...
  transform: ${({ currentActive }) =>
    currentActive &&
    `translate(${Object.keys(TAB_MENU).indexOf(currentActive) * 100}%, 0)`};
`;
profile
#프론트엔드

0개의 댓글