[격주회고] 탭 전환 효과 구현하기 (2부)

후니훈·2023년 9월 9일
0

격주회고

목록 보기
2/2

Previously On...

1부에서, 저희는 useRef를 사용해서 탭 메뉴의 전환 효과를 구현하는 방법에 대해서 이야기 해 봤습니다. 아직 안 보셨다구요?
얼른 보고 오세요! → 1부 보러가기

아무튼, useRef를 사용해서 탭 전환 효과를 구현한 것을 열심히 자랑하면서 다니다가, 다음과 같은 질문들을 받았습니다.

  • useRef를 굳이 사용해야 했나요?
  • 해당 탭 메뉴가 다른 컴포넌트에서도 사용된다고 가정하면, 위 코드는 재사용성이 떨어지지 않나요?


그냥 내가 만들었다고...자랑하고 싶었을 뿐인데...🥹
물론 듣고 보니 그렇네? 라는 생각이 들기도 했고, 그래서 다른 방법으로 구현해 보기로 했습니다.

useRef, 왜 많이 쓰면 안 좋아요?

useRef 를 지양하는 이유에는 여러 가지가 있겠지만, 대표적으로 다음과 같은 이유들 때문에 사용하지 않는다고 해요.

  1. 직관성 감소: useRef는 주로 DOM 요소에 직접 접근할 때 사용됩니다. 그러나 React는 주로 선언적인 방식을 통해 UI를 구성하는 데 초점을 맞추고 있습니다. useRef를 과도하게 사용하면 코드의 직관성이 떨어질 수 있습니다.

  2. 리렌더링 제어: useRef가 가리키는 값은 변경되더라도 컴포넌트를 리렌더링하지 않습니다. 때로는 이것이 바람직할 수 있지만, 상태의 변경에 따라 UI가 바뀌어야 하는 경우에는 useStateuseReducer와 같은 다른 훅을 사용하는 것이 더 적절합니다.

  3. 사이드 이펙트: useRef를 통해 직접 DOM을 조작하는 것은 예기치 않은 사이드 이펙트를 초래할 수 있습니다. React의 흐름 밖에서 DOM을 조작하면 예측하지 못한 문제가 발생할 수 있습니다.

  4. 재사용성 감소: 컴포넌트에서 useRef를 사용하여 DOM 요소나 다른 값들에 직접 접근하는 경우, 해당 컴포넌트의 재사용성이 감소할 수 있습니다.

결론적으로, useRef는 꼭 필요한 경우에만 사용하면 좋다는 거군요. 그렇다면 useRef를 사용하지 않고 탭 메뉴를 구현해 볼까요?

useRef를 사용하지 않기

useRef 대신 useState를 사용하는 방법도 있겠지만, 그것보단 CSS만을 사용해서 만드는 게 더 재사용성이 높을 것 같아서 CSS를 사용해서 만드는 방법을 찾아보던 중, 사이드프로젝트를 진행하는 팀원분께서 컴포넌트를 만들어서 공유해 주셨습니다! 🥹

그럼 한번 알아볼까요?

export const StepBar = ({ currentStep, howManyTab }) => {
  const marginLeft = `calc((100% / ${howManyTab}) * ${currentStep - 1})`;

  return (
    <Bar>
      <CurrentStepBar style={{ marginLeft, transition: "margin-left 0.5s" }} howManyTab={howManyTab}/>
    </Bar>
  );
};

const Bar = styled.div`
  width: 88%;
  height: 5px;
  background-color: var(--gray100-color);
  margin: 0 auto;
  border-radius: 5px;
`;

const barStyles = css<{ howManyTab: number }>`
  width: ${(props) => `calc(100% / ${props.howManyTab})`};
  height: 5px;
  background-color: var(--main-color);
  box-shadow: 0 4px 4px rgba(0, 0, 0, 0.3);
  border-radius: 10px;
`;

const CurrentStepBar = styled.div<{ howManyTab: number }>`
  ${(props) => props.howManyTab && barStyles}
`;

카테고리별 항목 아래의 주황색 바는 CurrentStepBar, 전체 배경이 되는 회색 바는 Bar컴포넌트입니다.
저희가 알아볼 건 CurrentStepBar의 위치를 계산하는 방법인데, props부터 살펴 볼게요.

  • currentStep: 현재 클릭된 스텝을 의미합니다.
  • howManyTab: 탭의 개수를 의미합니다.

margin-left를 주는 방식을 사용해서, CurrentStepBar를 처음 위치에서 밀어내도록 해 위치를 지정하게 됩니다.

const marginLeft = `calc((100% / ${howManyTab}) * ${currentStep - 1})`;

그리고 그 margin-left의 크기는 이렇게 calc를 사용해서 결정하게 됩니다.

그렇다면 CurrentStepBar 의 width는 어떻게 결정할까요?

  width: ${(props) => `calc(100% / ${props.howManyTab})`};

탭의 개수를 사용해서 등분해 주게 됩니다.

이렇게 끝나면 좋았겠지?

그럼 이제 탭 메뉴까지 잘 만들었으니, 예쁜 결과물이 나오겠죠? 아이 신나 바로 npm run start 해버리기~

나한테 왜 그래!!!
카테고리 메뉴와 CurrentStepBar가 미묘하게 안 맞는 상황이 발생하고 말았습니다 🥲
문제를 해결하기 위해서 이런 저런 시도를 해 봤는데, 개발자 도구로 확인해 보니 다음과 같은 문제를 찾을 수 있었습니다.

버튼의 크기가 미묘하게 달랐던 것입니다! 생각해 보면, 버튼의 width를 따로 지정하지 않았습니다. 이 상황에서, 제가 해결해야 하는 문제는 두 가지였습니다.

  • 버튼 내부의 텍스트와 상관 없이, 버튼의 크기가 균일했으면 좋겠다.
  • 브라우저 창의 폭이 늘어날 경우, 모든 버튼이 균일한 비율로 커졌으면 좋겠다.

그리고 이 두 가지 문제를 한 번에 해결해 줄 수 있는 속성이 바로 flex-grow입니다.

사실 조금 더 정확히 말하자면, 단축 속성인 flex를 사용했습니다.

flex CSS 속성은 하나의 플렉스 아이템이 자신의 컨테이너가 차지하는 공간에 맞추기 위해 크기를 키우거나 줄이는 방법을 설정하는 속성입니다. flex는 flex-grow, flex-shrink, flex-basis의 단축 속성입니다.

MDN의 설명에 따르면, 이런 속성이네요. flex 속성에는 한 개에서 세 개의 값을 줄 수 있는데, 숫자 하나면 주면 다음과 같이 설정됩니다:

  • flex-grow: 숫자만 지정하면 해당 숫자가 flex-grow의 값이 됩니다.
  • flex-shrink: 생략되어서, 기본 값인 1이 됩니다.
  • flex-basis: 생략되어서, 기본 값인 auto가 됩니다.

그래서 button에 flex: 1 속성을 주게 되면, 다음과 같은 결과를 얻을 수 있습니다.

이렇게 해서 탭 메뉴를 구현해 봤습니다. 야호~
앞으로 탭 메뉴를 만들 때, 이런 식으로 구현하면 될 것 같네요!😁

profile
개발과 친해지고 싶은 개발자

1개의 댓글

comment-user-thumbnail
2023년 9월 25일

멋진데요

답글 달기