Vue Component Architecture
Vue Component Architecture는 Vue.js의 component-based UI model 하위에서, SFC·Composition API·reactive state·lifecycle로 component 내부를 구성하고 Props·emitted events·Slots로 component 사이의 외부 contract를 노출하는 모델이다. 이 페이지가 답하는 질문은 component 내부 책임과 component 사이의 data/content 흐름을 어떻게 분리하는가다.
Component 캡슐화와 Parent-Child Contract
- Component는 UI, state, behavior를 캡슐화한 재사용 단위다. Vue는 독립적이고 재사용 가능한 UI/logic 단위로 nested component tree를 구성한다.[1]
- SFC는 component의
<template>,<script>,<style>을.vue파일에 응집한다.[2] - Parent-child contract는 세 축으로 나뉜다.
| Contract | 방향 | 책임 |
|---|---|---|
| Props | Parent -> Child | Parent가 소유한 data를 read-only input으로 전달한다. |
| Emitted events | Child -> Parent | Child interaction을 알리고 parent가 state 변경을 결정한다. |
| Slots | Parent -> Child template | Parent가 content 또는 일부 layout을 주입한다. |
SFC 내부 구성: Option API와 Composition API
- 03.Vuejs-Component.pdf는 SFC가 template, script, style을 하나의
.vue파일에 모으는 구조임을 보여준다.[2:1] - 같은 source는 Option API와 Composition API를 비교하고,
setup()/<script setup>에서 state, computed, watch, lifecycle logic을 구성하는 방법을 다룬다.[2:2] setup()은 component instance가 생성되기 전에 실행되므로 그 안에서 instancethis를 사용할 수 없다.[2:3]
Component 사이의 public contract
- Props는 child가 소유하지 않는 외부 input이다. child가 prop을 직접 mutate하면 parent render가 다시 값을 덮어쓸 수 있으므로, local state가 필요하면 prop을 초기값으로 삼거나 event를 emit해 parent가 변경하게 한다.[1:1] 이 경계를 무시하고 child가 parent-owned state를 직접 수정하면(Props mutation), ownership과 update path가 흐려진다.
defineProps()는<script setup>에서 props declaration을 위한 compile-time macro다. declaration 없이 전달된 attribute가 prop처럼 동작한다고 가정하지 않는다.[1:2]- 04.Vuejs-Component심화.pdf는 Props를 parent에서 child로 전달되는 read-only data로 설명한다.[3]
- Child가 변경을 요청할 때는 Props를 직접 수정하지 않고
defineEmits()로 event와 payload를 전달한다.[3:1] 여러 중간 component가 의미 없이 Props와 event를 relay하는 event tunneling은 이 contract를 지키면서도 흔히 나타나는 실수라는 것은 이 page의 해석이다. - Slot은 child template 안의 content insertion point이며, named/scoped slot으로 여러 영역과 child data를 노출할 수 있다.[3:2] slot name과 제공되는 slot props를 명확히 하지 않으면(implicit slot contract) parent template이 child 구현에 암묵적으로 의존하게 된다는 것은 이 page의 해석이다.
Ownership과 contract가 설계의 핵심인 이유
Component reuse의 핵심은 파일을 작게 나누는 것이 아니라 소유권과 contract를 명시하는 것이라고 볼 수 있다. State ownership은 변경을 승인하는 component에 두고, 하위 component는 Props로 읽고 event로 의도를 알린다. Markup/content variation이 문제라면 data Props를 늘리기보다 Slot으로 composition boundary를 제공하는 편이 낫다. 이 구분을 유지하면 component API가 "data input, behavior output, content input"으로 읽혀 변경 영향을 추적하기 쉬워진다는 관점에서 유용하다.
다른 개념과의 관계
Component architecture는 Vue Reactivity 위에서 성립한다 — Props와 local state 변경이 rendering에 반영되는 것은 reactivity 메커니즘 덕분이다. Template에서 binding과 event listener를 선언할 때는 Vue Directives를 사용한다. Vue Advanced Components는 이 Props/Emit/Slots 선택 절차를 실제 component 설계에 적용하는 pattern이고, Vue Router는 route component를 이 architecture 위의 view hierarchy에 배치한다.
경계
- Component architecture는 local UI boundary를 다룬다. Application-wide state management는 현재 source 범위를 넘는다.
- Provide/Inject와 dynamic component는 Vue component API지만, 이번 두 source에서 다루지 않았으므로 source-backed 핵심 모델에 포함하지 않는다.
- Composition API는 component 간 통신 방법이 아니라 component 내부 logic 조직 방법이다.
메커니즘
<!-- ItemButton.vue -->
<script setup>
const props = defineProps({ title: String })
const emit = defineEmits(['action'])
</script>
<template>
<button @click="emit('action', props.title)">
<slot>{{ title }}</slot>
</button>
</template>
<!-- Parent.vue -->
<ItemButton title="저장" @action="handleSave">
<strong>변경 저장</strong>
</ItemButton>
flowchart LR Parent[Parent owner] Child[Child component] Content[Parent content] Parent -- "Props: read-only data" --> Child Child -. "Emit: interaction + payload" .-> Parent Content -- "Slot: markup/content" --> Child
절충점
- Props가 너무 많으면 component 책임이 넓거나 data model이 잘못 분리됐을 가능성이 있다 — 특히 layout variation마다 boolean Props를 추가하는 방식(Boolean Props explosion)은 조합 가능한 상태 수를 급격히 늘린다.
- Event 종류와 payload가 지나치게 많으면 child가 parent workflow를 과도하게 알고 있을 수 있다.
- Slot은 layout reuse를 높이지만, slot name과 slot props가 많아지면 template contract를 이해하기 어려워진다.
관련
- Vue.js: component model을 포함하는 root concept이다.
- Vue app instance: state, computed, watch, lifecycle의 runtime 책임을 설명한다.
- Vue Advanced Components: reusable component contract를 설계하는 적용 pattern이다.
출처
테스트 질문
- Props, emitted events, Slots는 각각 어떤 방향과 책임을 가지는가?
- Composition API와 component communication을 같은 문제로 보면 안 되는 이유는 무엇인가?
- Props를 직접 변경하지 않고 event를 사용하는 이유는 무엇인가?
vue-component-basics.md, vue-component-props.md — "Components allow us to split the UI into independent and reusable pieces"; props의 one-way data flow와 mutation 경고,
defineProps()가<script setup>compile-time macro라는 설명 ↩︎ ↩︎ ↩︎03.Vuejs-Component.pdf — SFC 구조, Option API/Composition API 비교,
setup()실행 시점 ↩︎ ↩︎ ↩︎ ↩︎04.Vuejs-Component심화.pdf — Props를 read-only data로, event를
defineEmits()로, Slot을 named/scoped content insertion point로 설명 ↩︎ ↩︎ ↩︎