[TIL] React - DropDown

중고신입개발자·2022년 2월 10일
7

React - DropDown Components 만들기

오늘은 ?

하루에 하나씩 작은 컴포넌트를 만들고 기존코드를 분해해서 단순화하는 업무를 맡았다. 취업 전에는 미니프로젝트를 만들면서 다양한 라이브러리나 기술을 경험하면서 대략적인 개발과정을 파악했다면, 취업 후에는 실질적으로 커스텀이 필요한 입력폼 관련 ui등을 만드는 업무를 하게 되었다.

현재 맡게된 프로젝트는 하나의 컴포넌트가 상당히 비대하고 서비스로직과 단순 ui 코드들이 혼재되어있어 파악이 어려웠다. 프로젝트의 ui가 버전업 되면서 다양한 기능이 추가되고 삭제되기를 반복하면서 기존에 있던 데드코드들을 삭제하고 좀더 나은 프론트코드를 만들기 위해 ui단을 분리하고 있다.

완성본

CodeSandBox에서 코드 보기
프로젝트 관련 youtube

제작과정

  1. 드롭다운 메뉴만들기
  2. react-transition-group으로 하위메뉴에서 트랜지션하는 기능만들기

1. 드롭다운 메뉴 만들기

open의 상태에 따라서 하위의 렌더링이 결정된다.

// nav
fnction NavItem(props) {
  const [open, setOpen] = useState(false);
  return (
    <li>
      <a href="#" className="icon-button" onClick={() => setOpen(!open)}>
        {props.icon}
      </a>
      {open && props.children}
    </li>
  );
}
<NavItem>
  {/** 하위에 있는 메뉴가 열립니다. **/}
  <ul>
    <li>메뉴1</li>  
    <li>메뉴2</li>  
    <li>메뉴3</li>  
    <li>메뉴4</li>  
  </ul>
</NavItem>

2. transition-group 적용하기

css transition 으로 렌더링을 판별하는 상태값 isOpen을 전달하고 classNames 값에 따라 트랜지션이 작동한다

1. 일반적인 드롭다운

function DropdownContainer(props) {
  const divRef = useRef(null);
  const [isOpen, setIsOpen] = useState(false);
  const [selected, setSelected] = useState(props.default);
  return (
    <div className="select" default={props.default}>
      <span onClick={() => setIsOpen(!isOpen)}>{selected}</span>

      <CSSTransition
        nodeRef={divRef}
        in={isOpen}
        unmountOnExit
        timeout={300}
        classNames="drop"
      >
        <DropdownList ref={divRef}>{props.children}</DropdownList>
      </CSSTransition>
    </div>
  );
}

// ref는 하위 컴포넌트에 전달할 수 없어서 forwardRef를 사용함
const DropdownList = React.forwardRef((props, ref) => {
  return (
    <ul ref={ref} className="dropdown-list">
      {props.children}
    </ul>
  );
});
drop-enter {
  opacity: 0;
  transform: translate(-50%, -30px);
}
.drop-enter-active {
  opacity: 1;
  transition: opacity 300ms, transform 300ms;
  transform: translate(-50%);
}
.drop-exit {
  opacity: 1;
}
.drop-exit-active {
  opacity: 0;

  transition: opacity 300ms, transform 300ms;
}

2. 하위메뉴가 좌우로 바뀌는 드롭다운 메뉴

부모 컴포넌트에서 state값으로 드롭다운의 현재 페이지를 저장한다. 그 후 자식 컴포넌트중 하나에서 페이지를 바꾸는 props (goToMenu) 값을 받아서 클릭시 goToMenu의 값으로 렌더링한다.

CSS Transition으로 서로가 mount/unmount를 교차하게 css 코드를 작성한다.

function DropdownMenu() {
  const [activeMenu, setActiveMenu] = useState("main");
  const [menuHeight, setMenuHeight] = useState(null);

  const calcHeight = (el) => {
    const height = el.offsetHeight;
    setMenuHeight(height);
  };

  function DropdownItem(props) {
    return (
      <a
        href="#"
        className="menu-item"
        onClick={() => props.goToMenu && setActiveMenu(props.goToMenu)}
      >
        <span className="icon-button">{props.leftIcon}</span>
        {props.children}
      </a>
    );
  }
  return (
    <div className="dropdown" style={{ height: menuHeight }}>
      {/* 첫번째 메뉴 */}
      <CSSTransition
        in={activeMenu === "main"}
        unmountOnExit
        timeout={500}
        onEnter={calcHeight}
        classNames="menu-primary"
      >
        <div className="menu">
          <DropdownItem goToMenu="setting">go to secondary</DropdownItem>
          <DropdownItem>My setting</DropdownItem>
        </div>
      </CSSTransition>

      {/* 두번째 메뉴 */}
      <CSSTransition
        in={activeMenu === "setting"}
        unmountOnExit
        timeout={500}
        onEnter={calcHeight}
        classNames="menu-secondary"
      >
        <div className="menu">
          <DropdownItem goToMenu="main">first</DropdownItem>
          <DropdownItem>My setting</DropdownItem>
          <DropdownItem>My setting</DropdownItem>
          <DropdownItem>My setting</DropdownItem>
          <DropdownItem>My setting</DropdownItem>
          <DropdownItem>My setting</DropdownItem>
          <DropdownItem>My setting</DropdownItem>
        </div>
      </CSSTransition>
    </div>
  );
}

.menu-primary-enter {
  transform: translateX(-110%);
}
.menu-primary-enter-active {
  transform: translateX(0%);
  transition: all var(--speed) ease;
}

.menu-primary-exit {
  position: absolute;
}
.menu-primary-exit-active {
  transform: translateX(-110%);
}

/****/
.menu-secondary-enter {
  transform: translateX(110%);
}
.menu-secondary-enter-active {
  transform: translateX(0%);
  transition: all var(--speed) ease;
}
.menu-secondary-exit {
  position: absolute;
}
.menu-secondary-exit-active {
  transform: translateX(110%);
}
profile
취업전에는 기술스택을, 취업후에는 고도화를 하자

0개의 댓글