민혁이의 IT스토리

[Typers 팀프로젝트] 메인페이지 - 메뉴 만들기 본문

팀프로젝트/개발

[Typers 팀프로젝트] 메인페이지 - 메뉴 만들기

FE_Minhyuk 2025. 4. 21. 21:01

 

오늘 개발 할 파트는 메뉴 입니다

 

 

 

목차 

  • 컨포넌트 분리
  • 코드 리뷰
  • 스스로 아쉬운점

컨포넌트 분리


header/
├── _component\nav/
│   ├── mainMenu/
│   │   ├── MainMenu.jsx
│   │   └── style.js
│   └── subMenu/
│       ├── SubMenu.jsx
│       └── style.js
├── Header.js
└── style.js

 

설명 : 메뉴가 헤더 부분에 위치하고 있기 때문에 헤더에서 부터 하위 컨포넌트도 뻗어 나가도록 파일을 분리하였습니다. 
header -> nav -> menu(Main,Sub)

 

 

 

 

 


코드 리뷰


 

<Header/>

내용 
: 헤더 컨포넌트는  로고,메뉴,로그인/회원가입 등을 담고 있습니다. 그중에서도 메뉴는 <S.Nav/> 로 되어있는 부분입니다.


 

코드 보기

더보기

Component

import React, { useState } from 'react';
import S from './style';
import { Link } from 'react-router-dom';
import SubMenu from './_component/nav/subMenu/SubMenu';
import MainMenu from './_component/nav/mainMenu/MainMenu';
const Header = () => {

    const mainMeuItems = [// 메인메뉴 
        {id : 0, title : '타이핑', url : '/'},
        {id : 1, title : '커스텀', url : '/'},
        {id : 2, title : '랭킹킹', url : '/'},
        {id : 3, title : '창작마당',url : '/'}
    ]

      const subMenuItems = [// 서브메뉴
        [
            {id : 0, title : '긴글연습', url : '/'},
            {id : 1, title : '짧은글연습', url : '/'},
            {id : 2, title : '낱말연습', url : '/'},
            {id : 3, title : '자리연습', url : '/'},
            {id : 4 , title : '미니게임', url : '/'},
        ],
        [
            {id : 0, title : '문장 커스텀', url : '/'},
            {id : 1, title : '단어 커스텀', url : '/'},
        ],
        [
            {id : 0, title : '타이핑 랭크전', url : '/'},
            {id : 1, title : '랭킹 보기 ', url : '/'},
        ],
        [
            {id : 0, title : '타이핑 랭크전', url : '/'},
            {id : 1, title : '랭킹 보기 ', url : '/'},
        ],
    ]


    const [ishover,setIsHover] = useState(false)

    const EnterMenu = () =>{
      setIsHover(true)
    }
  
    const LeaveMenu = () =>{
      setIsHover(false)
    }

    return (
        <S.Header>
          <S.Logo>
              logo
          </S.Logo>
          <S.Nav 
          	{/*마우스를 hover하면 실행*/}
            onMouseEnter={EnterMenu} 
            {/*마우스를 out하면 실행*/}
            onMouseLeave={LeaveMenu}
            >
            <S.Menu>
            {/*mainMeuItems의 개수 만큼 만복*/}
              {mainMeuItems.map(MenuItem=>(
                <MainMenu MenuItem={MenuItem} />
              ))}
              </S.Menu>
              <S.SubMenuBox ishover={ishover}>
                  <S.SubMenu>
                  {/*subMenuItems 개수 만큼 만복*/}
                    {subMenuItems.map(data=> (
                      <SubMenu children={data}/>
                    ))}
                  </S.SubMenu>
              </S.SubMenuBox>
            </S.Nav>
        </S.Header>
    );
};

export default Header;

 

Style

import styled from "styled-components";
import { felxRow } from "../../../../global/common";
const S = {}

S.Header = styled.header`
    width: 100%;
    height: 110px;
    ${felxRow}
    position: relative;
    border-bottom:1px solid #6E58FF ;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);

`

S.Main = styled.div`
    width: 100%;
    height: calc(100vh - 130px);
`

S.Logo = styled.div`
    flex: 2;
    height: 100%;
    text-align: center;
    line-height: 130px;
`

S.Nav = styled.div`
    flex: 5;
    height: 100%;
`

S.Menu = styled.ul`
    width: 100;
    height: 100%;
    display: flex;
    justify-content: space-around;
    align-items: center;
`
S.SubMenuBox = styled.div`
    position: absolute;
    top: 101%;
    left: 0;
    width: 100%;
    background-color: #fff;
    border-radius: 0 0 20px 20px;
    overflow: hidden;
    max-height: ${({ ishover }) => (ishover ? '300px' : '0')};
    opacity: ${({ ishover }) => (ishover ? '1' : '0')};
    transform: translateY(0);
    transition: max-height 0.4s ease, opacity 0.4s ease;
    border-top: 2px solid transparent;
`


S.SubMenu = styled.div`
    width: 50%;
    ${felxRow}
    justify-content: space-around;
    margin-left: 20%;
    
`

S.ButtonContainer = styled.div`
    flex: 3;
    height: 110px;
    right: 0;
`

S.Auth = styled.div`
    width: 50%;
    height: 100%;
    font-size: 20px;
    font-weight: 500;
    ${felxRow}
    justify-content: center;
    align-items: center;
    color: #6E58FF;
    & > a{
        color: #6E58FF;
    }
`

export default S;

 

 

<MainMenu/>

 

header  참고 코드


const mainMeuItems = [// 메인메뉴 url경로 미정
        {id : 0, title : '타이핑', url : '/'},
        {id : 1, title : '커스텀', url : '/'},
        {id : 2, title : '랭킹킹', url : '/'},
        {id : 3, title : '창작마당',url : '/'}
    ]


 

위와 같은 배열을 <header/> 컨포넌트에서 map을 통해 객체 하나하나를  <MainMenu/>로 props해줍니다.

코드 보기

더보기

Component

const MainMenu = ({MenuItem}) => {
    const {id, title , url} = MenuItem
    return (
        <S.MenuText key={id}><Link to={url}>{title}</Link></S.MenuText>
    );
};

 

Style

const S = {}

S.MenuText = styled.li`
    font-size: 25px;
    flex: 1;
    height: 100%;
    text-align: center;
    line-height: 110px;
    &>a{
        display: block;
        width: 100%;
        height: 100%;
        color: #6E58FF;;
    }

    & > a:hover{
        border-bottom: 7px solid #6E58FF;
    }
`

 

<SubMenu/>

 

header 참고 코드


      const subMenuItems = [// 서브메뉴
        [
            {id : 0, title : '긴글연습', url : '/'},
            {id : 1, title : '짧은글연습', url : '/'},
            {id : 2, title : '낱말연습', url : '/'},
            {id : 3, title : '자리연습', url : '/'},
            {id : 4 , title : '미니게임', url : '/'},
        ],
        [
            {id : 0, title : '문장 커스텀', url : '/'},
            {id : 1, title : '단어 커스텀', url : '/'},
        ],
        [
            {id : 0, title : '타이핑 랭크전', url : '/'},
            {id : 1, title : '랭킹 보기 ', url : '/'},
        ],
        [
            {id : 0, title : '타이핑 랭크전', url : '/'},
            {id : 1, title : '랭킹 보기 ', url : '/'},
        ],
    ]

구조는 mainMeuItems과 유사하지만 배열이 한번 더 감싸고 있다는 부분이 차이가 있습니다.

 

코드 보기

더보기

Component

import React from 'react';
import { Link } from 'react-router-dom';
import S from './style';
const SubMenu = ({children}) => {

    return (
        <ul>
            {children.map(data =>(
                <S.SubText key={data.id}>
                    <Link to={data.url}>{data.title}</Link>
                    </S.SubText>
            ))}
        </ul>
    );
};

export default SubMenu;


Style

import styled from "styled-components";
import { parentSize } from "../../../../../../../global/common";

const S = {}

S.SubText = styled.li`
    width: 150px;
    height: 40px;
    & > a{
        display: block;
        ${parentSize}
        line-height: 40px;
        text-align: center;
        color : #6E58FF;
    }
`

export default S;

 

 

 

 

 

 

 

 

 

완성 영상

 

동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.

 

 

 

 

 

 

 


스스로 아쉬웠던 점


메인메뉴와 서브메뉴를 만들 때 객체를 사용하였는데,
객체의 구조가 크게 다르지 않아서 메뉴 컨포넌트를 하나 만들어서 
코드 재사용성이 높은 컨포넌트를 만드는게 좋았을 것 
같다는 생각이 이 글을 작성하면서 들었습니다..
하지만 또 메인메뉴와 서브메뉴를 나누는 것도 나쁘지 않다는 생각이 들어서 
구현은 끝냈지만 완성이 덜 된 느낌이였습니다.