Vue3 프로젝트에서 사이드바를 구현하며 마주친 문제들:
프로젝트의 사이드바는 다음과 같은 구조로 되어 있었습니다:
src/
├── App.vue (전역 사이드바 스타일)
├── components/
│ ├── layouts/
│ │ └── MainLayout.vue (레이아웃 관리)
│ └── common/
│ ├── SidebarMenu.vue (메뉴 아이템)
│ └── SidebarFooter.vue (하단 메뉴)
/* App.vue, SidebarMenu.vue, SidebarFooter.vue 모두에 존재 */
.sidebar-menu-item {
display: flex;
align-items: center;
padding: 12px 20px;
color: #ecf0f1;
/* ... 동일한 스타일 반복 ... */
}
/* MainLayout.vue */
.main-sidebar.collapsed .sidebar-menu .sidebar-menu-item { ... }
/* SidebarMenu.vue */
.main-sidebar.collapsed .sidebar-menu-item { ... }
/* src/sidebar.css */
/* 사이드바 관련 CSS 변수 */
:root {
--sidebar-width-expanded: 250px;
--sidebar-width-collapsed: 60px;
--sidebar-bg-primary: #333333;
--sidebar-bg-secondary: #2b2b2b;
--sidebar-text-color: #ecf0f1;
--sidebar-hover-bg: rgba(255, 255, 255, 0.1);
--sidebar-active-bg: rgba(255, 255, 255, 0.15);
--sidebar-active-border: #F36545;
--sidebar-transition: all 0.3s ease;
}
/* 기본 메뉴 아이템 스타일 */
.sidebar-menu-item {
display: flex;
align-items: center;
padding: 12px 20px;
color: var(--sidebar-text-color);
text-decoration: none;
transition: var(--sidebar-transition);
cursor: pointer;
position: relative;
overflow: hidden;
white-space: nowrap;
}
.sidebar-menu-item:hover {
background: var(--sidebar-hover-bg);
padding-left: 25px;
}
.sidebar-menu-item.active {
background: var(--sidebar-active-bg);
border-left: 4px solid var(--sidebar-active-border);
}
/* 사이드바 접힘 상태 */
.main-sidebar.collapsed .sidebar-menu-item {
justify-content: center;
padding: 8px 0;
width: var(--sidebar-width-collapsed);
height: 36px;
}
.main-sidebar.collapsed .menu-text {
opacity: 0;
visibility: hidden;
width: 0;
margin-left: 0;
}
/* 모바일 반응형 */
@media (max-width: 767px) {
.menu-text {
opacity: 1 !important;
visibility: visible !important;
width: auto !important;
margin-left: 10px !important;
}
.sidebar-menu-item {
justify-content: flex-start !important;
padding: 12px 20px !important;
width: 100% !important;
}
}
/* src/style.css */
@import './sidebar.css'; /* 맨 위에 추가 */
/* 기존 스타일들... */
<!-- SidebarMenu.vue -->
<style scoped>
/* 중복 스타일 모두 제거하고 고유 스타일만 유지 */
.sidebar-wrapper {
width: 100%;
}
</style>
<!-- SidebarFooter.vue -->
<style scoped>
.sidebar-footer-wrapper {
width: 100%;
}
</style>
// App.vue
import { useRouter } from 'vue-router'
setup() {
const router = useRouter()
const mainLayoutRef = ref(null)
// 라우터 네비게이션 후 모바일에서 사이드바 닫기
router.afterEach(() => {
mainLayoutRef.value?.closeSidebar()
})
return {
mainLayoutRef
// ... 기존 반환값
}
}
// MainLayout.vue
setup(props, { emit, expose }) {
// ... 기존 코드
const closeSidebar = () => {
if (isMobile.value) {
isSidebarOpen.value = false
}
}
// 외부에서 사용할 수 있도록 메서드 노출
expose({
closeSidebar,
toggleSidebar,
isMobile
})
// ... 나머지 코드
}
<!-- App.vue -->
<template>
<MainLayout
ref="mainLayoutRef"
:page-title="pageTitle"
@sidebar-toggle="handleLayoutSidebarToggle">
<!-- slots... -->
</MainLayout>
</template>
// CSS Modules 예시
import styles from './Sidebar.module.css'
<div :class="styles.menuItem">
// useSidebar.js
export function useSidebar() {
const isOpen = ref(false)
const isCollapsed = ref(false)
const toggle = () => {
// 로직
}
return {
isOpen,
isCollapsed,
toggle
}
}
.sidebar-menu-item {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
/* 서브메뉴 슬라이드 애니메이션 */
.submenu-enter-active,
.submenu-leave-active {
transition: all 0.3s ease;
}
.submenu-enter-from,
.submenu-leave-to {
opacity: 0;
transform: translateY(-10px);
}
사이드바 같은 공통 컴포넌트는 프로젝트가 커질수록 관리가 어려워집니다. 초기에 잘 구조화하고, 지속적으로 리팩토링하는 것이 중요합니다.
이번 리팩토링을 통해 얻은 교훈:
1. DRY 원칙 준수: 중복 코드는 즉시 제거
2. 관심사 분리: 레이아웃, 스타일, 로직을 명확히 분리
3. 사용자 경험 우선: 특히 모바일 환경에서의 UX 고려
태그: #Vue3 #CSS #Refactoring #MobileUX #Sidebar #ComponentDesign