
Vite + SFC + 컴포넌트 패턴 한 번에 훑기
Vite는 Vue 3 공식 권장 개발 환경이다.
개발 서버 속도가 매우 빠른 번들러 겸 빌드 도구
개발 시:
빌드 시:
Vue CLI 대비
npm create vue@latest
No 로 두고 시작해도 충분cd 프로젝트명
npm install
npm run dev
http://localhost:5173src/
assets/ # 이미지, CSS
components/ # 공용 컴포넌트
App.vue # 루트 컴포넌트
main.js # 앱 엔트리
index.html # 앱 틀
index.html
#app 영역에 Vue 앱이 마운트되는 기본 HTMLmain.js
createApp(App).mount('#app') 같은 코드가 위치App.vue
components/
Vue에서는 .vue 파일 하나에 템플릿 + 로직 + 스타일을 모아 관리한다.
<template>
<!-- UI -->
</template>
<script setup>
// 로직 (상태, 함수, props, emit 등)
</script>
<style scoped>
/* 스타일 (해당 컴포넌트에만 적용) */
</style>
<style scoped> 를 사용하면 해당 컴포넌트에만 스타일이 적용된다<script setup> 의 장점Vue 3 Composition API 를 쓰는 가장 권장되는 방식.
장점 정리:
| 장점 | 설명 |
|---|---|
| 코드 간결 | export default, setup() 선언 필요 없음 |
| 자동 import | defineProps, defineEmits 자동 인식 |
| 성능 최적화 | 컴파일 단계에서 더 효율적인 코드로 변환 |
| 자연스러운 문법 | Composition API 를 기본 문법처럼 사용 가능 |
예시:
<script setup>
import { ref } from 'vue';
const count = ref(0);
const increase = () => {
count.value++;
};
</script>
Vue 컴포넌트 파일 이름은 PascalCase 권장
MyButton.vue, UserCard.vue 등HTML 기본 태그와 구분하기 위한 컨벤션
components/로 분리부모 → (props) → 자식
자식 → (emit) → 부모
이 흐름을 지키면 구조가 단순해지고 유지보수가 쉬워진다.
부모가 자식에게 값을 전달하는 공식적인 통로
특징
<script setup>
const props = defineProps({
message: String,
count: Number
});
</script>
<template>
<div>
<p>{{ message }}</p>
<p>{{ count }}</p>
</div>
</template>
예시(옵션 확장):
const props = defineProps({
message: {
type: String,
default: '기본 메시지'
},
count: {
type: Number,
required: true
}
});
<script setup>
const emit = defineEmits(['add']);
const onClick = () => {
emit('add', 10); // 'add' 이벤트 + payload 전달
};
</script>
<template>
<button @click="onClick">추가</button>
</template>
부모 쪽 사용 예:
<MyButton @add="handleAdd" />
const handleAdd = value => {
console.log('자식이 넘긴 값:', value);
};
부모 → (props) → 자식 : 값 전달
자식 → (emit) → 부모 : 이벤트 전달
이 패턴을 지키면 데이터 방향이 명확해지고 버그를 줄일 수 있다.
v-model – 안전한 양방향 바인딩컴포넌트에서도 v-model 을 지원하게 만들 수 있다.
자식 컴포넌트
modelValue라는 props로 현재 값 받기update:modelValue 이벤트 emit부모 컴포넌트
<MyInput v-model="username" /> 처럼 사용<script setup>
const props = defineProps(['modelValue']);
const emit = defineEmits(['update:modelValue']);
const onInput = event => {
emit('update:modelValue', event.target.value);
};
</script>
<template>
<input :value="props.modelValue" @input="onInput" />
</template>
부모에서는 그냥:
<MyInput v-model="username" />
이렇게 쓰면 된다.
슬롯은 부모가 자식 컴포넌트 안의 특정 영역을 채우는 기능이다.
자식:
<template>
<div class="card">
<slot></slot>
</div>
</template>
부모:
<MyCard>
<p>이 내용이 slot 안으로 들어간다</p>
</MyCard>
자식:
<template>
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</template>
부모:
<MyLayout>
<template #header>
<h1>헤더</h1>
</template>
<p>본문 내용</p>
<template #footer>
<p>푸터</p>
</template>
</MyLayout>
Modal 컴포넌트
header, body, footer 를 slot 으로 공개Layout 컴포넌트
left, right 영역을 slot 으로 제공해서 다양한 레이아웃 구성Slot 은 기본적으로 “부모 → 자식 콘텐츠 전달” 구조지만,
Scoped Slot 을 사용하면 자식 데이터를 부모에게도 넘길 수 있다.
<template>
<div>
<slot :user="user"></slot>
</div>
</template>
<script setup>
const user = {
name: '홍길동',
age: 20
};
</script>
<UserCard>
<template #default="{ user }">
<p>{{ user.name }} ({{ user.age }})</p>
</template>
</UserCard>
Props는 항상 한 단계씩 내려 보내야 한다.
Provide/Inject 는 중간 단계를 건너뛰고 필요한 자식에게 바로 데이터를 내려준다.
import { provide, ref } from 'vue';
const theme = ref('dark');
provide('theme', theme);
보통 루트 컴포넌트나 상위 레이아웃에서 제공한다.
import { inject } from 'vue';
const theme = inject('theme');
어디서든 같은 키로 inject 하면 같은 ref/값을 공유한다.
단, 남용하면 구조 파악이 어려워지므로
이 정도로 역할을 나누는 편이 좋다.
이 글에서 정리한 것
<template>, <script setup>, <style scoped>)v-model 구현 패턴여기까지 이해하면, Vite + SFC 기반 Vue 3 프로젝트에서
컴포넌트 구조를 어떻게 나누고 데이터를 어떻게 주고받을지 큰 그림을 잡을 수 있다.