Vue가 뭔지, 그리고 Vue의 기본 문법이 궁금하시다면?
2025.11.18 - [Web/Vue] - Vue.js 입문 : 기본 철학과 기본 문법 정리
Vue.js 입문 : 기본 철학과 기본 문법 정리
1. Vue.js는 어떤 프레임워크인가? Vue.js는 사용자 인터페이스(UI)를 만들기 위한 자바스크립트 프레임워크입니다.HTML, CSS, JavaScript 위에서 동작하기 때문에,기존 웹 개발 흐름을 크게 벗어나지 않으
sproutinghye.tistory.com
1. 컴포넌트란? 컴포넌트가 중요한 이유?
Vue를 조금만 써도 금방 깨닫게 되는 게 있습니다.
바로 컴포넌트 위주로 구성되어 있다는 것입니다.
Vue 앱은 화면을 잘게 쪼갠 컴포넌트들의 트리 구조로 이루어져 있습니다.
각 컴포넌트는 독립된 UI 조각이면서, 재사용 가능한 작은 단위 모듈이라고 보면 됩니다.
- 버튼도 하나의 컴포넌트
- 카드 리스트, 모달, 헤더, 사이드바도 컴포넌트
- 이 컴포넌트들이 부모-자식 관계를 이루면서 트리 형태로 앱을 구성
Vue 공식 가이드에서도 컴포넌트를 UI를 독립적이고 재사용 가능한 조각으로 쪼개는 핵심 단위라고 정의하고 있습니다.
이번 글에서는 이 컴포넌트 시스템의 기초인
- 싱글 파일 컴포넌트(SFC) 구조
- 부모 -> 자식 데이터 흐름: props
- 자식 -> 부모 이벤트 흐름: $emit
- 간단한 폴더 구조 및 설계 팁
까지 한 번에 정리해보겠습니다.
2. 싱글 파일 컴포넌트(SFC)란?
Vue 컴포넌트는 보통 .vue 확장자를 가진 싱글 파일 컴포넌트(Single File Component, SFC) 형식으로 작성합니다.
기본 구조는 아래와 같습니다.
<!-- MyButton.vue -->
<template>
<button class="my-btn" @click="handleClick">
{{ label }}
</button>
</template>
<script>
export default {
name: 'MyButton',
props: {
label: {
type: String,
required: true
}
},
methods: {
handleClick() {
this.$emit('click')
}
}
}
</script>
<style scoped>
.my_btn {
padding: 8px 16px;
border-radius: 4px;
}
</style>
각 영역의 역할은 아래와 같습니다.
- <template>: 화면 구조(HTML)
- <script>: 데이터, 로직, 이벤트 처리
- <style>: 해당 컴포넌트에 대한 스타일(CSS)
Vue 공식 스타일가이드에서는 SFC 상단 태그 순서를 일관되게 유지할 것을 권장합니다.
특히 <style>을 항상 마지막에 두는 패턴이 많이 사용됩니다.
프로젝트에 들어가면 보통 이런 느낌의 구조를 많이 보게 됩니다.
src/
components/
common/
MyButton.vue
Modal.vue
layout/
Header.vue
Footer.vue
user/
UserProfile.vue
UserCard.vue
컴포넌트 단위로 나누어두면 찾기도 쉽고, 재사용도 편해집니다.
컴포넌트 구조를 체계적으로 설계하면 프로젝트가 커져도 유지보수가 훨씬 수월해집니다.
3. props: 부모에서 자식으로 데이터 내려보내기
부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달할 때 사용하는 것이 props입니다.
공식 문서에서는 props를 부모에서 자식으로 단방향 데이터 바인딩(one-way-down binding)으로 설명합니다.
즉, 값이 부모에서만 내려가고, 자식이 직접 props를 바꿔서는 안 됩니다.
- 사용 예시
[ 부모 컴포넌트 ]
<!-- Parent.vue -->
<template>
<div>
<UserCard :user="user" :is-active="isActive" />
</div>
</template>
<script>
import UserCard from './UserCard.vue'
export default {
components: { UserCard },
data() {
return {
user: {
name: '홍길동',
age: 30
},
isActive: true
}
}
}
</script>
[ 자식 컴포넌트 ]
<!-- UserCard.vue -->
<template>
<div class="user-card" :class="{ active: isActive }">
<p>이름: {{ user.name }}</p>
<p>나이: {{ user.age }}</p>
</div>
</template>
<script>
export default {
name: 'UserCard',
props: {
user: {
type: Object,
required: true
},
isActive: {
type: Boolean,
default: false
}
}
}
</script>
포인트는 두 가지입니다.
1) 부모에서 :user="user" 같은 형태로 속성 바인딩
2) 자식에서 props 옵션을 명시적으로 선언 (타입, 필수 여부 등)
Vue 공식 문서에서도 props는 항상 명시적으로 선언하라고 강조합니다.
이게 디버깅, 문서화, IDE 지원에 모두 도움이 됩니다.
4. $emit: 자식에서 부모로 이벤트 올려보내기
데이터 흐름은 주로 부모 -> 자식 방향인데,
반대로 자식에서 부모에게 "이런 일이 일어났어요"라고 알려야 할 때도 있습니다.
이때 사용하는 것이 커스텀 이벤트와 $emit입니다.
emit은 '방출하다'라는 뜻으로, 자식에서 부모로 이벤트를 방출한다고 생각하면 쉽겠죠.
- 기본 패턴
1) 자식 컴포넌트에서 $emit('이벤트이름', payload)
2) 부모 컴포넌트에서 @이벤트이름="핸들러"로 받기
- 예시
[ 자식 컴포넌트 ]
<!-- ChildButton.vue -->
<template>
<button @click="handleClick">
{{ label }}
</button>
</template>
<script>
export default {
name: 'ChildButton',
props: {
label: String
},
methods: {
handleClick() {
this.$emit('clicked', { time: Date.now() })
}
}
}
</script>
[ 부모 컴포넌트 ]
<!-- Parent.vue -->
<template>
<div>
<ChildButton label="저장" @clicked="onSaveClicked" />
</div>
</template>
<script>
import ChildButton from './ChildButton.vue'
export default {
components: { ChildButton },
methods: {
onSaveClicked(payload) {
console.log('저장 버튼 클릭 시각:', payload.time)
// 실제 저장 로직 실행
}
}
}
</script>
클릭 이벤트가 발생했을 때 자식 컴포넌트에서 부모 컴포넌트로 payload(Date.now())를 전달하고 있는 것을 알 수 있습니다.
Vue 공식 문서에서는 이 패턴을 '부모-자식 간 데이터 흐름의 기본 메커니즘: props는 아래로, 이벤트는 위로(props down, events up)'라고 설명합니다.
이 규칙을 숙지하면 대부분의 Vue 컴포넌트 통신은 큰 혼란 없이 해결됩니다.
5. 예제
실제 프로젝트에서는 props와 $emit을 항상 같이 쓰게 됩니다.
예를 들어 투두 리스트 아이템 컴포넌트를 생각해보겠습니다.
[ 자식: TodoItem.vue ]
<template>
<li :class="{ done: todo.done }">
<input
type="checkbox"
:checked="todo.done"
@change="$emit('toggle', todo.id)"
/>
<span>{{ todo.text }}</span>
<button @click="$emit('remove', todo.id)">삭제</button>
</li>
</template>
<script>
export default {
name: 'TodoItem',
props: {
todo: {
type: Object,
required: true
}
}
}
</script>
<style scoped>
.done span {
text-decoration: line-through;
}
</style>
[ 부모: TodoList.vue ]
<template>
<ul>
<TodoItem
v-for="item in todos"
:key="item.id"
:todo="item"
@toggle="handleToggle"
@remove="handleRemove"
/>
</ul>
</template>
<script>
import TodoItem from './TodoItem.vue'
export default {
components: { TodoItem },
data() {
return {
todos: [
{id: 1, text: 'Vue 공부하기', done: false},
{id: 2, text: '운동하기', done: true}
]
}
},
methods: {
handleToggle(id) {
const target = this.todos.find(t => t.id == id)
if (target) target.done = !target.done
},
handleRemove(id) {
this.todos = this.todos.filter(t => t.id !== id)
}
}
}
</script>
흐름을 정리해보자면,
1) 부모 -> 자식 :todo="item"으로 todo 객체를 내려보냄
2) 자식 -> 부모 체크박스/삭제 버튼에서 $emit('toggle', id), $emit('remove', id)
3) 부모에서 @toggle, @remove 이벤트를 받아 실제 데이터 배열을 수정
이 패턴이 전형적인 컴포너트 통신 구조입니다.
6. 컴포넌트 구조 및 설계 팁
실무에서 Vue 컴포넌트가 수십, 수백 개 되기 시작하면
어디에 뭘 만들지가 중요한 고민거리가 됩니다.
여러 자료와 스타일가이드를 종합하면, 보통 이런 원칙들이 많이 쓰입니다.
1️⃣ 공통 컴포넌트 vs 도메인 컴포넌트
src/
components/
common/ # 버튼, 모달, 입력창 같은 공통 UI
layout/ # Header, Footer, Sidebar 같은 레이아웃
user/ # 유저 도메인 관련 컴포넌트
product/ # 상품 도메인 관련 컴포넌트
- common: 여러 화면에서 반복해서 쓸 수 있는 UI 위젯 등
- 도메인 폴더(user, product 등): 특정 도메인 로직이 들어간 컴포넌트
2️⃣ 파일 이름 & 컴포넌트 이름
- 파일명은 PascalCase: UserCard.vue, TodoList.vue
- export default { name: 'UserCard', ... }처럼 name도 통일
Vue 공식 스타일가이드는 컴포넌트 이름을 명확하고 일관도게 짓는 걸을 추천합니다.
그래야 디버깅, Vue DevTools, 협업에서 모두 편해집니다.
정리
- 컴포넌트는 Vue 앱의 기본 단위이며, 트리 구조로 앱을 구성
- 싱글 파일 컴포넌트 (SFC)
- <template>: 화면
- <script>: 로직
- <style>: 스타일
- 부모 -> 자식: props
- 단방향(one-way down) 데이터 흐름
- 자식에서 props 직접 수정 금지
- 자식 -> 부모: $emit
- 커스텀 이벤트로 부모에게 알림
- props down, events up 패턴
- 폴더 구조
- common, layout, 도메인별 폴더 등으로 나누기
- 이름 규칙을 통일해서 협업과 유지보수성을 높이기
출처
'Framework > Vue' 카테고리의 다른 글
| Vuex 기초: Vue 전역 상태 관리 (state, getters, mutations, actions, modules) (1) | 2025.11.20 |
|---|---|
| Vue Router와 Axios 활용해 라우팅 + API 연동으로 SPA 만들기 (0) | 2025.11.20 |
| Vue Router 기초 (Vue 2 + Vue Router 3) (1) | 2025.11.19 |
| Vue2 Options API : data, methods, computed, watch (0) | 2025.11.19 |
| Vue.js 입문 : 기본 철학과 기본 문법 정리 (0) | 2025.11.18 |