project | 유튜브 클론코딩 팀플 회고

녕녕·2023년 1월 25일
0

회고log🐾

목록 보기
13/18
post-thumbnail

👀사이트

👀작업기간

2023.01.16~01.20 (본 진행 기간)
2023.01.25~02.07 (2차 수정 기간)

👀팀원구성

프론트엔드 4인

👀기술스택

👀진행과정

아래의 내용을 학습하기 위해 프로젝트를 진행했다.

  • youtube api 를 사용해보고, 댓글/검색/네비게이션 바 구현을 학습
  • 또 공통 컴포넌트와 상태관리, 중첩 라우팅으로 레이아웃 구현 학습

나는 여기서 레이아웃과 헤더, 사이드바 구현을 맡았다.

👀구현기능

1.시연

다양한 분기처리가 필요했다.

1.1 페이지별

  • 메인 페이지에서 햄버거 메뉴를 누르면 사이드바가 축소되지만,
  • 상세 페이지에서 햄버거 메뉴를 누르면 사이드바가 넓게 뜬다.

1.2 뷰포트 사이즈별

  • 1300px 이상에서는 기본 넓은 사이드바가 보이고, 메뉴를 누르면 작은 사이드바가 보이지만

  • 1299px 이하에서는 기본 작은 사이드바가 보이고, 메뉴를 누르면 모달 사이드바가 보인다.

  • 792px 이하에서는 기본 사이드바는 없고, 메뉴를 누르면 모달 사이드바가 보인다.

2.구현코드

2.1 레이아웃

// App.js

import { Outlet } from 'react-router-dom';
import Layout from './components/Layout/Layout';

// 생략

function App() {
  return (
    <Layout>
      <Outlet />
    </Layout>
    
// 생략
// Layout.jsx


import { useLocation } from 'react-router-dom';
import Header from './Header/Header';
import Sidebar from './Sidebar/Sidebar';

export default function Layout({ children }) {
  const [sidebar, setSidebar] = useState(false);

  // 현재 영상 디테일 페이지인지?
  const location = useLocation();
  const findDetailPage = location.pathname.slice(0, 6) === '/watch';

  return (
    <>
      <Header setSidebar={setSidebar} findDetailPage={findDetailPage} />
      <div className={`${styles.layout} ${findDetailPage ? styles.detailPage : null}`}>
        <div className={findDetailPage ? styles.sidebarNone : null}>
          <Sidebar sidebar={sidebar} setSidebar={setSidebar} />
        </div>
        <main className={`${styles.outlet} ${sidebar ? styles.btnTrue : styles.btnFalse}`}>{children}</main>
      </div>
    </>
  );
}
  • outlet을 사용해 레이아웃을 구성했다.
  • HeaderSidebar에는 useLocation을 사용해 페이지별 설정을 달리해줬다.
  • 햄버거 메뉴와 로고가 있는 부분은 별도 컴포넌트로 뺐다. 넓은 사이드바가 열리든 모달 사이드바가 열리든 열리지 않든, 모두 하나의 컴포넌트를 사용했다. 그러기 위해 sidebar라는 공통 state를HeaderSidebar로 drilling 해줬다.

2.2 헤더

// Header.jsx

//생략

import HeaderMenu from './HeaderMenu';
import SidebarModal from '../Sidebar/SidebarModal';

//생략

export default function Header({ setSidebar, findDetailPage }) {
  const [modal, setModal] = useState(false);
  
  // 생략
  
  return (
  
  // 생략
    
  <HeaderMenu
    setModal={setModal}
    setSidebar={setSidebar}
    menuBtn={findDetailPage ? 'openModal' : resize <= 1300 ? 'openModal' : 'openSidebar'}/>
  <SidebarModal modal={modal} setModal={setModal} />
}
  // 생략
  
  • 헤더 안에서는 모달 사이드바를 관리했고, 다른 컴포넌트에서는 넓은 사이드바와 작은 사이드바를 관리했다.
  • menuBtn이라는 속성을 줘서, 상세페이지이거나 메인페이지의 1300px 이하면 모달 사이드바가 뜨도록 했다. 메인페이지의 1300px 이상이면 넓은 사이드바나 좁은 사이드바가 뜨도록 했다.
// HeaderMenu.jsx

// 생략

export default function HeaderMenu({ setModal, setSidebar, menuBtn }) {
  const btnClick = () => {
    switch (menuBtn) {
      case 'openModal':
        // 태블릿과 모바일 사이즈, 메뉴 버튼으로 모달 사이드바 열기
        (function openModal(e) {
          setModal((e) => !e);
        })();
        break;
      case 'openSidebar':
        // 데스크탑 사이즈, 메뉴 버튼 클릭
        (function openSidebar(e) {
          setSidebar((e) => !e);
        })();
        break;
      default:
        break;
    }
  };

  return (
    <div className={styles.headerMenu}>
      <BsList className={styles.headerIcon} size="24" onClick={btnClick} />
      <Link to={'/'} className={styles.logo}>
        <img src={logo} alt="youtube logo" />
        <sup>KR</sup>
      </Link>
    </div>
  );
}
  • Header 컴포넌트에서 의도한대로 switch 문으로 분기처리했다.
// SidebarModal.jsx

// 생략

import SidebarLarge from './SidebarLarge';
import HeaderMenu from '../Header/HeaderMenu';

export default function SidebarModal({ modal, setModal }) {
  const modalRef = useRef(null);
  useOnClickOutside(modalRef, () => setModal(false));

  return (
    <nav className={modal ? `${styles.modalNav} ${styles.open}` : styles.modalNav} ref={modalRef}>
      <HeaderMenu setModal={setModal} menuBtn={'openModal'} />
      <SidebarLarge />
    </nav>
  );
  • Header안에 HeaderMenu에서 설정한 대로, SidebarModal이 열리고 닫힌다. open 이라는 클래스를 state의 조건에 따라 설정해주어 구현했다.

2.3 사이드바

// Sidebar.jsx

import SidebarLarge from './SidebarLarge';
import SidebarSmall from './SidebarSmall';

export default function Sidebar({ sidebar }) {
  const resize = useWindow();
  return (
    <>{resize >= 1300 ? sidebar ? <SidebarSmall /> : <SidebarLarge /> : resize >= 792 ? <SidebarSmall /> : null}</>
  );
  • 모달로 뜨지 않는, 넓은 사이드바와 좁은 사이드바는 Sidebar 컴포넌트에서 관리했다.
// SidebarLarge.jsx

// 생략

const iconList = {
  list1: [
    { icon: <RiHome5Fill />, title: '홈' },
    { icon: <BsCollectionPlay />, title: '구독' },
    { icon: <MdOutlineVideoLibrary />, title: '보관함' },
  ],
  list2: [
    { icon: <MdOutlineRestore />, title: '시청기록' },
    { icon: <AiOutlinePlaySquare />, title: '내 동영상' },
    { icon: <MdOutlineQueryBuilder />, title: '나중에 볼 동영상' },
    { icon: <MdKeyboardArrowDown />, title: '더보기' },
  ],
  
  // 생략
};

const linkList = [
  { title: '정보' },
  { title: '보도자료' },
  // 생략
];

export default function SidebarLarge() {
  return (
    <nav className={styles.largeNav}>
      <ol>
        {iconList.list1.map((list) => (
          <button key={list.title} className={list.title === '홈' ? styles.homeIcon : null}>
            <span className={styles.icon}>{list.icon}</span>
            {list.title}
          </button>
        ))}
      </ol>
      // 생략
  • 사이드바에 들어가는 내용들은 배열로 정리한 후, map을 돌려 구현했다.

👀마무리

협업

두번째 협업 프로젝트라서 개발환경 세팅부터 배포까지 프론트엔드 팀원끼리 어떻게 손발을 맞춰야하는지 알고 있었기에, 이 전에 비해 많은 것들이 수월했다. 다같이 오프라인으로 작업한 것도 한 몫한 것 같다.

구현

간단할 줄 알았던 사이드바와 헤더는 진행하다보니 분기처리를 많이 고려해야했고, 컴포넌트도 재사용 때문에 코드를 다 뒤집고 다시 작업했다. 보기보다 많이 어려웠던 구현이었다. 개발이 신기한게 어려워보이는 게 쉬울 때도 있고, 쉬워보이는게 어려울 때도 있다. 그래서 방심할 수 없다. 이번 프로젝트에서 했던 많은 삽질로 prop drilling 을 익숙하게 사용할 수 있게 됐다!

profile
FE Developer | 차근차근

0개의 댓글