🔷 부모는 자식에게 데이터를 전달(Pass Props)하며, 자식은 자신에게 일어난 일을 부모에게 알림(Emit event)
🔷 Props
: 부모 컴포넌트 → 자식 컴포넌트
🔷 One-Way Data Flow
: 모든 props는 자식 속성과 부모 속성 사이에 하향식 단방향 바인딩을 형성 (one-way-down binding)
🔷 Props 특징
🤔 왜 단방향이어야 하는가?
- 하위 컴포넌트가 실수로 상위 컴포넌트의 상태를 변경하여 앱에서의 데이터 흐름을 이해하기
어렵게 만드는 것을 방지하기 위함이다.
🔷 App → Parent → ParentChild 컴포넌트 관계 작성
<template>
<div>
<h1>최상위 컴포넌트</h1>
<Parent />
</div>
</template>
<script setup>
import Parent from './components/Parent.vue';
</script>
<style scoped>
</style>
<template>
<div>
<h2>부모 컴포넌트</h2>
<ParentChild />
</div>
</template>
<script setup>
import ParentChild from './ParentChild.vue';
</script>
<style scoped>
</style>
<template>
<div>
<h3>자식 컴포넌트</h3>
</div>
</template>
<script setup>
</script>
<style scoped>
</style>
🔷 Props 작성
<!-- Parent.vue -->
<template>
<div>
<ParentChild my-msg="message" />
</div>
</template>
🔷 Props 선언
1. 문자열 배열을 사용한 선언
<!-- ParentChild.vue -->
<script setup>
defineProps(['myMsg'])
</script>
2. 객체를 사용한 선언
❗ 객체 선언 문법 사용을 권장한다.
- prop에 타입을 지정하는 것은 컴포넌트를 가독성이 좋게 문서화 하는데 도움이 되며, 다른 개발자가 잘못된 유형을 전달할 때에 브라우저 콘솔에서 경고를 출력하도록 한다.
- 추가로 prop에 대한 유효성 검사로써 활용 가능하다.
<!-- ParentChild.vue -->
<script setup>
defineProps({
// 여러 타입 허용
propB: [String, Number],
// 문자열 필수
propC: {
type: String,
required: true
},
// 기본 값을 가지는 숫자형
propD: {
type: Number,
default: 10
},
})
</script>
🔷 prop 데이터 사용
<template>
<div>
<h3>자식 컴포넌트</h3>
<p>{{myMsg}}</p>
</div>
</template>
<script setup>
//문자열 배열 형태로 props를 받아서 사용 가능
//defineProps(['myMsg', ]);
//defineProps와 객체를 사용한 선언 (확장이 용이 하기에 권장)
const props = defineProps({
myMsg: String,
})
console.log(props);
console.log(props.myMsg);
</script>
<style scoped>
</style>
<template>
<div>
<h2>부모 컴포넌트</h2>
<input type="text" v-model="name"/>
<!-- my-msg에 담아 정적으로 할당 -->
<ParentChild my-msg="message123" />
</div>
</template>
<script setup>
import ParentChild from './ParentChild.vue';
</script>
<style scoped>
</style>
🔷 Props 세부사항
1. Props Name Casing (Props 이름 컨벤션)
▪ 선언 및 템플릿 참조 시 → camelCase
<p>{{ myMsg }}</p>
defineProps({
myMsg: String
})
▪ 자식 컴포넌트로 전달 시 → kebab-case
<ParentChild my-msg="message" />
❗ 기술적으로 camelCase도 가능하나 HTML 속성 표기법과 동일하게 kebab-case로 표기할 것을 권장한다.
2. Static props & Dynamic props
1) Dynamic props 정의
2) Dynamic props 선언 및 출력
3) Dynamic props 출력 확인
🔷 한 단계 더 내려보내면서 동적 할당쓰기
<template>
<div>
<h4>손자 컴포넌트</h4>
<p>{{myMsg}}</p>
</div>
</template>
<script setup>
defineProps({
myMsg: String
})
</script>
<style scoped>
</style>
<template>
<div>
<h3>자식 컴포넌트</h3>
<p>{{myMsg}}</p>
<p>{{dynamicProps}}</p>
<!-- v-bind를 활용하여 props를 동적으로 할당 -->
<ParentGrandChild :my-msg="myMsg" />
</div>
</template>
<script setup>
import ParentGrandChild from '@/components/ParentGrandChild.vue'
//문자열 배열 형태로 props를 받아서 사용 가능
//defineProps(['myMsg', ]);
//defineProps와 객체를 사용한 선언 (확장이 용이 하기에 권장)
const props = defineProps({
myMsg: String,
dynamicProps: String,
})
console.log(props);
console.log(props.myMsg);
</script>
<style scoped>
</style>
<template>
<div>
<h2>부모 컴포넌트</h2>
<input type="text" v-model="name"/>
<!-- my-msg에 담아 정적으로 할당 및 v-bind를 활용하여 props를 동적으로 할당 -->
<ParentChild my-msg="message123" :dynamic-props="name"/>
</div>
</template>
<script setup>
import { ref } from 'vue';
import ParentChild from './ParentChild.vue';
const name = ref('Alice');
</script>
<style scoped>
</style>
부모 -> 자식은 정적 할당 및 동적 할당, 자식 -> 손자는 동적 할당을 사용하였다.
❗ 정적 & 동적 props 주의 사항
정적 props는 문자열로써의 “1”을 전달한다면 동적 props는 숫자로써의 1을 전달한다.
Pass Props -> Emit event 이후
부모가 prop 데이터를 변경하도록 소리쳐야 한다!
🔷 $emit()
$
표기는 Vue 인스턴스나 컴포넌트 내에서 제공되는 전역 속성이나 메서드를 식별하기 위한 접두어🔷 emit 메서드 구조
$emit(event, ...args)
event
→ 커스텀 이벤트 이름
args
→ 추가 인자
🔷 이벤트 발신 및 수신 (Emitting and Listening to Events)
$emit
을 사용하여 템플릿 표현식에서 직접 사용자 정의 이벤트를 발신<button @click="$emit('someEvent')">클릭</button>
<ParentComp @some-event="someCallback" />
🔷 이벤트 발신 및 수신 과정
ParentChild에서 someEvent 라는 이름의 사용자 정의 이벤트를 발신
-> ParentChild의 부모 Parent는 v-on 을 사용하여 발신된 이벤트를 수신
-> 수신 후 처리할 로직 및 콜백함수 호출
💡
defineEmits()
를 사용하여 명시적으로 발신할 이벤트를 선언할 수 있다.
🔷 이벤트 선언 방식으로 추가 버튼 작성 및 결과 확인
<template>
<div>
<h3>자식 컴포넌트</h3>
<p>{{myMsg}}</p>
<p>{{dynamicProps}}</p>
<!-- emit으로 someEvent 전달 -->
<button @click="$emit('someEvent')">클릭</button>
<button @click="buttonClick">클릭2</button>
<ParentGrandChild :my-msg="myMsg" />
</div>
</template>
<script setup>
import ParentGrandChild from '@/components/ParentGrandChild.vue'
//문자열 배열 형태로 props를 받아서 사용 가능
//defineProps(['myMsg', ]);
//defineProps와 객체를 사용한 선언 (확장이 용이 하기에 권장)
const props = defineProps({
myMsg: String,
dynamicProps: String,
})
//명시적으로 emit 이벤트를 정의
const emit = defineEmits(['someEvent']);
const buttonClick = () => {
emit("someEvent");
}
console.log(props);
console.log(props.myMsg);
</script>
<style scoped>
</style>
<template>
<div>
<h2>부모 컴포넌트</h2>
<input type="text" v-model="name"/>
<!-- my-msg에 담아 정적으로 할당 및 v-bind를 활용하여 props를 동적으로 할당 -->
<ParentChild @some-event="someCallBack" my-msg="message123" :dynamic-props="name"/>
</div>
</template>
<script setup>
import { ref } from 'vue';
import ParentChild from './ParentChild.vue';
const name = ref('Alice');
const someCallBack = () => {
console.log('자식 컴포넌트의 이벤트를 수신')
}
</script>
<style scoped>
</style>
이벤트 발신 및 수신이 잘 되는 모습
🔷 이벤트 인자 (Event Arguments)
<template>
<div>
<h3>자식 컴포넌트</h3>
<p>{{myMsg}}</p>
<p>{{dynamicProps}}</p>
<!-- v-bind를 활용하여 props를 동적으로 할당 -->
<button @click="$emit('someEvent')">클릭</button>
<button @click="buttonClick">클릭2</button>
<button @click="emitArgsFunc">클릭3</button>
<ParentGrandChild :my-msg="myMsg" />
</div>
</template>
<script setup>
import ParentGrandChild from '@/components/ParentGrandChild.vue'
//문자열 배열 형태로 props를 받아서 사용 가능
//defineProps(['myMsg', ]);
//defineProps와 객체를 사용한 선언 (확장이 용이 하기에 권장)
const props = defineProps({
myMsg: String,
dynamicProps: String,
})
//명시적으로 emit 이벤트를 정의
const emit = defineEmits(['someEvent', 'emitArgs']);
const buttonClick = () => {
emit("someEvent");
}
const emitArgsFunc = () => {
emit("emitArgs", 1, 2, 3);
}
console.log(props);
console.log(props.myMsg);
</script>
<style scoped>
</style>
<template>
<div>
<h2>부모 컴포넌트</h2>
<input type="text" v-model="name"/>
<!-- my-msg에 담아 정적으로 할당 및 v-bind를 활용하여 props를 동적으로 할당 -->
<ParentChild @emit-args="getNumber" @some-event="someCallBack" my-msg="message123" :dynamic-props="name"/>
</div>
</template>
<script setup>
import { ref } from 'vue';
import ParentChild from './ParentChild.vue';
const name = ref('Alice');
const someCallBack = () => {
console.log('자식 컴포넌트의 이벤트를 수신')
}
const getNumber = (...args) => {
console.log(args)
}
</script>
<style scoped>
</style>
누를 때 마다 이벤트와 함께 인자가 부모에게 전달된다.
💡 emit 이벤트도 객체 선언 문법으로 작성 가능하다.
emit('submit', { email, password })
🔷 emit 이벤트 실습
1. ParentGrendChild 에서 이름 변경을 요청하는 이벤트 발신
<input type="text" v-model="newName">
<button @click="updateNameFunc">click!</button>
const emit = defineEmits(['updateName']);
const newName = ref('');
const updateNameFunc = () => {
emit('updateName', newName.value);
}
2. 이벤트 수신 후 이름 변경을 요청하는 이벤트 발신 (ParentChild.vue)
<ParentGrandChild @update-name="updateNameFunc" :my-msg="myMsg" />
const emit = defineEmits(['updateName']);
const updateNameFunc = (newName) => {
emit('updateName', newName);
}
3. 이벤트 수신 후 이름 변수 변경 메서드 호출, 해당 변수를 prop으로 받는 모든 곳에서 자동 업데이트
<ParentChild @update-name="updateNameFunc" />
const updateNameFunc = (newName) => {
name.value = newName;
}
손자 컴포넌트에서 input에 text를 담고 click 이벤트를 보내면 부모에서 받아 prop의 값을 바꿔 아래에 다시 전달한다.
prop drilling을 리액트를 배울 때 이해하기 어려웠는데, 이번에 확실히 알고 간다. vue에서의 prop drilling 해결을 어서 익히고 싶다는 마음 뿐이다.