👨🏻💻
Day 55 Vue 컴포넌트 연동
February 14, 2025
컴포넌트 연동
Vue 컴포넌트 연동에 핵심이 되는 3가지 개념은 props, emit 그리고 v-model이 있다.
- props : 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달하는 Read-Only 속성, defineProps() 사용
- emit : 자식 컴포넌트에서 부모 컴포넌트로 이벤트를 전달하는 방법, defineEmits(), $emit() 사용
- v-model : 부모와 자식 간의 양방향 바인딩으로 props + emit을 합친 개념, defineModel() 사용
props
- Props는 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달하는 방법
- defineProps를 통해 자식 컴포넌트에서 부모에서 받을 props를 정의
- html 속성 처럼 camelCase가 아닌 kebab-case로 이름을 정의하는 것을 권장 (greeting-message=“안녕!“)
- 단방향 데이터 흐름을 유지해야 하며, props로 받은 값은 자식에서 직접 수정할 수 없다. (read only)
- 단방향 데이터 흐름일지라도 부모에서 props를 변경하면 이를 감지하여 자동으로 업데이트 된다.
예제 1
// 부모 컴포넌트
<script setup>
import ChildComponent1 from './ChildComponent1.vue';
</script>
<template>
<div>
<h3>Parent Component1</h3>
<ChildComponent1 title="Vue의 Props" :count="10" />
</div>
</template>
// 자식 컴포넌트
<script setup>
// props 선언부
// props를 js에서 활용할 일이 있으면 props를 선언하여 받는다.
const props = defineProps({
title: String,
count: Number,
});
console.log(props.count);
console.log(props.title);
</script>
<template>
<div class="child">
<b>{{ title }}</b
><br />
Count: {{ count }}<br />
</div>
</template>예제 2
// 부모 컴포넌트 (Parent Component2)
<script setup>
import ChildComponent2 from './ChildComponent2.vue';
const title = 'Vue Props 예제입니다.';
const count = 10;
const active = true;
const items = ['Apple', 'Banana', 'Cherry'];
const user = { name: 'Alice', age: 25 };
const today = new Date();
const callback = () => alert('Callback function!');
</script>
<template>
<ChildComponent2
:title="title"
:count="count"
:active="active"
:items="items"
:user="user"
:today="today"
:callback="callback"
/>
</template>
// 자식 컴포넌트 (ChildComponent2)
<script setup>
defineProps({
title: String, // 문자열
count: Number, // 숫자
active: Boolean, // 불리언
items: Array, // 배열
user: Object, // 객체
callback: Function, // 함수
today: Date,
});
</script>
<template>
<div class="child">
<h3>{{ title }}</h3>
count : {{ count }} <br />
active : {{ active ? 'Y' : 'N' }} <br />
item : <template v-for="(item, index) in items" :key="index">{{ item }},</template> <br />
user : {{ user.name }}, {{ user.age }}<br />
today : {{ today.toLocaleString() }}<br />
<button @click="callback">callback</button>
</div>
</template>유효성 검사
// 부모 컴포넌트 (Parent Component3)
<script setup>
import ChildComponent3 from './ChildComponent3.vue';
</script>
<template>
<ChildComponent3
:prop-a="10"
prop-b="Hello World"
prop-c="required"
:prop-d="null"
prop-g="성공"
/>
</template>
// 자식 컴포넌트 (ChildComponent3)
<script setup>
defineProps({
// 기본 타입 검사 (null, undefined 허용)
propA: Number,
// 여러 가지 가능한 타입
propB: [String, Number],
// 필수 문자열
propC: {
type: String,
required: true,
},
// 필수이지만 널 허용 문자열
propD: {
type: [String, null],
required: true,
},
// 기본값이 있는 숫자
propE: {
type: Number,
default: 100,
},
propG: {
validator(value) {
// 값은 이 문자열 중 하나와 일치해야 합니다
return ['성공', '경고', '위험'].includes(value);
},
},
});
</script>
<template>
<div class="child">
<h2>Child Component</h2>
propA (Number): {{ propA }} <br />
propB (String or Number): {{ propB }} <br />
propC (Required String): {{ propC }} <br />
propD (Required String or Null): {{ propD }} <br />
propE (Number with Default 100): {{ propE }} <br />
propG ('성공', '경고', '위험'): {{ propG }} <br />
</div>
</template>emit
- Vue의 emit은 자식 컴포넌트에서 부모 컴포넌트로 이벤트를 전달하는 메커니즘이다.
- Vue3에서 도입된 $emit을 기반으로 활용하는 문법으로 단축된 emit 활용을 지원한다.
- 부모에서는 @some-event와 같이 kebab-case로 선언하고, 자식에는 camelCase로 호출하여도 문제없다.
- 또한 vue2 부터 활용하던 emit 패턴으로 defineEmits을 통해 emit을 활용하는 방법도 존재
emit 기본 예제
// 부모 컴포넌트
<script setup>
import EmitBasic1 from './EmitBasic1.vue';
const handleSomeEvent = () => {
alert('call handleSomeEvent');
console.log('call handleSomeEvent');
};
</script>
<template>
<EmitBasic1 @some-event="handleSomeEvent" />
</template>
// 자식 컴포넌트
<script setup></script>
<template>
<div class="child">
<h3>Child</h3>
<!-- someEvent : 부모에서 정의 이벤트의 이름 -->
<button @click="$emit('someEvent')">Click me</button>
</div>
</template>데이터 전달 예제 - emit
// 부모 컴포넌트
<script setup>
import EmitData1 from './EmitData1.vue';
const handleSomeEvent = (count, text) => {
alert(`전달 된 데이터 : ${count}, ${text}`);
console.log(`전달 된 데이터 : ${count}, ${text}`);
};
</script>
<template>
<EmitData1 @some-event="handleSomeEvent" />
</template>
// 자식 컴포넌트
<script setup>
import { ref } from 'vue';
const count = ref(100);
const text = ref('안녕하세요?');
</script>
<template>
<div class="child">
<h3>Child</h3>
count : <input type="text" v-model="count" /> <br />
text : <input type="text" v-model="text" /> <br />
<button @click="$emit('someEvent', count, text)">emit</button>
</div>
</template>데이터 전달 예제 - defineEmits
// 부모 컴포넌트
<script setup>
import EmitBasic2 from './EmitBasic2.vue';
const handleSomeEvent = () => {
alert('call handleSomeEvent');
console.log('call handleSomeEvent');
};
</script>
<template>
<EmitBasic2 @some-event="handleSomeEvent" />
</template>
// 자식 컴포넌트
<script setup>
import { defineEmits } from 'vue';
// 이벤트 정의
const emit = defineEmits(['someEvent']);
const handleClick = () => {
emit('someEvent');
};
</script>
<template>
<div class="child">
<h3>Child</h3>
<button @click="handleClick">Click me</button>
</div>
</template>v-model - defineModel()
- v-model을 컴포넌트에서 사용하여 양방향 바인딩을 구현할 수 있다.
- 또한 named를 붙여 하나가 아닌 여러 개의 v-model을 활용할 수 있다.
- Vue 3.4부터는 defineModel() 매크로를 사용하는 것을 권장한다.
다수의 v-model 활용
// 부모 컴포넌트
<script setup>
import { ref } from 'vue';
import ChildComponent2 from './ChildComponent2.vue';
const count = ref(0);
const text = ref('');
</script>
<template>
<div>
count : {{ count }} <br />
text : {{ text }} <br />
<ChildComponent2 v-model:count="count" v-model:text="text" />
</div>
</template>
// 자식 컴포넌트 (ChildComponent2)
<script setup>
const count = defineModel('count');
const text = defineModel('text');
const increment = () => {
count.value++;
};
</script>
<template>
<div class="child">
부모 count : {{ count }} <button @click="increment">click</button><br />
<input type="text" v-model="text" />
</div>
</template>Provide / Inject (전역 속성 공유)
- vue에서 props 없이 컴포넌트 간 데이터를 공유하는 방법 제공한다.
- provide()를 사용하여 부모가 값을 제공하고, inject()를 사용하여 자식이 값을 사용한다.
- provide(): 부모 컴포넌트에서 데이터를 제공 하는 메소드
반응형 예제
// 부모 컴포넌트
<script setup>
import { provide, ref } from 'vue';
import ChildComponent2A from './ChildComponent2A.vue';
import ChildComponent2B from './ChildComponent2B.vue';
const location = ref('북극');
const updateLocation = (newLocation) => {
location.value = newLocation;
};
// provide를 통해 자식 컴포넌트에 값과 update 함수 제공
provide('location', { location, updateLocation });
</script>
<template>
<div>
<h3>부모 컴포넌트</h3>
현재 위치 : {{ location }} <br />
<ChildComponent2A />
<ChildComponent2B />
</div>
</template>
// 자식 컴포넌트 (ChildComponent2A)
<script setup>
import { inject } from 'vue';
// 부모에서 전달한 location과 update함수 선언
const { location, updateLocation } = inject('location');
</script>
<template>
<div class="child">
<h3>자식 컴포넌트 2A</h3>
현재 위치 : {{ location }} <br />
<button @click="updateLocation('남극')">남극 변경</button>
</div>
</template>
// 자식 컴포넌트 (ChildComponent2B)
<script setup>
import { inject } from 'vue';
// 부모에서 전달한 location과 update함수 선언
const { location, updateLocation } = inject('location');
</script>
<template>
<div class="child">
<h3>자식 컴포넌트 2B</h3>
현재 위치 : {{ location }} <br />
<button @click="updateLocation('북극')">북극 변경</button>
</div>
</template>