지난 'Vue 기본기 다지기 2편'에서는 Vite를 이용해 개발 환경을 설정하고 Vue의 기본적인 템플릿 문법에 대해 알아보았습니다.
이번 시간에는 Vue 애플리케이션을 구조적으로 만들어가는 핵심 개념인
컴포넌트(Component)
에 대해 깊이 있게 탐구해 보겠습니다.
특히 부모 컴포넌트와 자식 컴포넌트가 어떻게 데이터를 주고받는지,
props
와
emit
의 역할과 중요성에 대해 풍부한 예제와 함께 자세히 설명하겠습니다.
웹 페이지를 만든다고 상상해 봅시다.
헤더, 사이드바, 게시물 목록, 푸터 등 여러 구역으로 나눌 수 있습니다.
만약 이 모든 것을 하나의 거대한 파일에 작성한다면 코드는 금세 복잡해지고 유지보수가 어려워질 것입니다.
컴포넌트는 바로 이 문제를 해결하기 위해 등장한 개념입니다.
UI를 기능별로 재사용 가능한 독립적인 조각으로 나눈 것이 바로 컴포넌트입니다.
예를 들어, '사용자 프로필' 컴포넌트를 한 번 잘 만들어두면, A 페이지에서도 보여주고, B 페이지에서도 가져다 쓸 수 있습니다.
이렇게 하면 코드의 재사용성이 극대화되고, 기능 단위로 개발할 수 있어 협업에도 유리합니다.
재사용 컴포넌트
🚀
부모에서 자식으로 데이터 전달하기 (Props) 🔗
컴포넌트를 만들다 보면, 부모 컴포넌트가 가진 데이터를 자식 컴포넌트에게 전달해야 할 때가 많습니다.
이때 사용되는 것이 바로
props
입니다.
Props는 부모가 자식에게 데이터를 물려주는 특별한 속성이라고 생각하면 쉽습니다.
자식 컴포넌트는 부모로부터 어떤 props를 받을지 미리 정의해야 합니다.
<script setup>
안에서
defineProps
매크로를 사용하여 이를 선언할 수 있습니다.
먼저 부모 컴포넌트
App.vue
를 만들어 보겠습니다.
username
이라는 데이터를 자식에게 전달할 것입니다.
/App.vue < script setup >
import { ref } from 'vue'
import GreetingMessage from './components/GreetingMessage.vue'
const parentUsername = ref ( 'promleeblog' )
</ script >
< template >
< GreetingMessage user = "Guest" />
< GreetingMessage : user = " parentUsername " />
</ template >
이제 데이터를 받을 자식 컴포넌트 GreetingMessage.vue
를 만듭니다.
/components/GreetingMessage.vue < script setup >
// 부모로부터 'user'라는 이름의 prop을 받을 것이라고 선언합니다.
const props = defineProps ([ 'user' ])
</ script >
< template >
< h2 >안녕하세요, {{ props.user }}님!</ h2 >
</ template >
위 예제에서 중요한 점은 두 가지입니다.
정적인 값("Guest")을 전달할 때는 user="Guest"
처럼 바로 입력합니다.
동적인 데이터(parentUsername
)를 전달할 때는 v-bind:
의 축약형인 :
를 사용하여 :user="parentUsername"
처럼 작성해야 합니다.
코드가 복잡해지면, 부모가 자식에게 어떤 타입의 데이터를 보내야 하는지, 또는 필수적으로 보내야 하는지를 명확히 하는 것이 중요합니다.
defineProps
에 배열 대신 객체를 전달하면, 각 prop에 대한 상세한 유효성 검사 규칙을 설정할 수 있습니다.
< script setup >
defineProps ({
// 타입만 검사
name: String,
// 타입과 필수 여부 검사
age: {
type: Number,
required: true
},
// 여러 타입 허용 및 기본값 설정
isPremium: {
type: [Boolean, String],
required: false ,
default: false
},
// 기본값이 객체나 배열인 경우 함수로 반환해야 합니다.
tags: {
type: Array,
default : () => [ '신규' , '일반' ]
}
})
</ script >
< template >
</ template >
이렇게 규칙을 정해두면, 다른 개발자가 이 컴포넌트를 사용할 때 어떤 props를 넘겨야 하는지 명확히 알 수 있습니다.
만약 규칙에 맞지 않는 데이터가 들어오면 Vue가 콘솔에 경고 메시지를 띄워주어 실수를 방지하는 데 큰 도움이 됩니다.
🚀
자식에서 부모로 신호 보내기 (Emit) 🔗
Props가 위에서 아래로 흐르는 데이터의 흐름이라면, 반대로 자식이 부모에게 무언가 알릴 필요도 있습니다.
예를 들어, 자식 컴포넌트에 있는 버튼이 클릭되었을 때 부모 컴포넌트의 데이터를 변경해야 하는 상황입니다. 이때 자식은 부모의 데이터를 직접 수정할 수 없습니다.
대신, 자식은 부모에게 특정 이벤트가 발생했음을 알리는
신호
를 보냅니다. 이것을
이벤트 발신(emit)
이라고 합니다.
자식 컴포넌트가 어떤 이벤트를 발생시킬 수 있는지
defineEmits
매크로를 사용해 미리 정의합니다.
사용자 이름을 입력받아 부모에게 알려주는
UsernameInput.vue
자식 컴포넌트를 만들어 보겠습니다.
/components/UsernameInput.vue < script setup >
import { ref } from 'vue'
// 'update'라는 이름의 이벤트를 발생시킬 것임을 선언합니다.
const emit = defineEmits ([ 'update' ])
const newName = ref ( '' )
function submitName () {
// 'update' 이벤트를 발생시키면서, newName의 값을 함께 보냅니다.
emit ( 'update' , newName.value)
}
</ script >
< template >
< input v-model = " newName " placeholder = "새로운 이름을 입력" />
< button @ click = " submitName " >이름 변경하기</ button >
</ template >
부모 컴포넌트 App.vue
에서는 자식이 발생시킨 @update
이벤트를 듣고 있다가, 이벤트가 발생하면 특정 함수를 실행합니다.
/App.vue < script setup >
import { ref } from 'vue'
import UsernameInput from './components/UsernameInput.vue'
const username = ref ( '기존 사용자' )
// 자식으로부터 받은 데이터(newName)로 부모의 데이터를 변경하는 함수
function changeUsername ( newName ) {
username.value = newName // 부모의 username을 자식으로부터 받은 newName으로 변경
}
</ script >
< template >
< h1 >현재 사용자: {{ username }}</ h1 >
< UsernameInput @ update = " changeUsername " />
</ template >
이렇게 하면 자식 컴포넌트의 버튼 클릭으로 부모 컴포넌트의 username
데이터가 안전하게 변경됩니다.
지금까지 살펴본 props
와 emit
의 동작 방식은 Vue의 매우 중요한 설계 원칙인 *단방향 데이터 흐름(One-Way Data Flow)*을 따릅니다.
이름 그대로 데이터가 한쪽 방향으로만 흐른다는 뜻입니다.
데이터는 위에서 아래로 (부모 → 자식)
오직 props
를 통해서만 데이터가 전달됩니다.
신호는 아래에서 위로 (자식 → 부모)
자식은 부모에게 emit
을 통해 이벤트만 알릴 뿐, 부모의 상태를 직접 바꾸지 않습니다. 상태를 바꾸는 책임은 항상 데이터를 소유한 부모에게 있습니다.
단방향 데이터 흐름은 애플리케이션의 데이터 흐름을 예측 가능하게 만듭니다.
만약 자식 컴포넌트가 마음대로 부모나 다른 컴포넌트의 데이터를 수정할 수 있다면, 데이터가 어디서 어떻게 변경되었는지 추적하기가 매우 어려워집니다.
이는 앱이 복잡해질수록 심각한 버그의 원인이 됩니다.
하지만 단방향 흐름을 지키면 데이터의
출처가 명확해지므로
문제가 발생했을 때 원인을 찾기 훨씬 수월해집니다.
데이터를 바꾸는 로직은 항상 데이터를 소유한 컴포넌트 안에 모여있기 때문입니다.
이번 포스팅에서는 Vue 애플리케이션을 구조화하는 핵심 단위인 컴포넌트의 개념에 대해 알아보았습니다.
부모가 자식에게 데이터를 전달하는
props
와 자식이 부모에게 이벤트를 알리는
emit
의 사용법을 예제와 함께 살펴보았습니다.
다음 'Vue 기본기 다지기 4편'에서는 컴포넌트 안에서 데이터를 다루는 더 강력한 방법인 반응형 상태 관리(ref
, reactive
)에 대해 알아보겠습니다.