컴포넌트에 등록할 수 있는 사용자 정의 속성.
간단히 말하면, 자식 컴포넌트에서 props를 선언하면 부모 컴포넌트에서 데이터(속성)를 전달할 수 있다.
위의 그림을 통해 직관적으로 알 수 있다.
부모 컴포넌트의 데이터를 자식 컴포넌트로 전달시키는 역할이다.
쉽게 예시 먼저 살펴보자.
<!-- 부모 컴포넌트 TheView.vue-->
<template>
<AppCard title="제목1" contents="내용1"> </AppCard>
</template>
<!--자식 컴포넌트 AppCard.vue-->
<template>
<p> {{ title }} </p> <!--제목1-->
<p> {{ contents }} </p> <!--내용1-->
</template>
<script setup>
export default {
props: ['title', 'contents'],
setup() {
return {}
}
}
</script>
부모 컴포넌트 TheView.vue
에서 자식 컴포넌트인 AppCard.vue
에 title, contents 데이터를 전달했고,
자식 컴포넌트인 AppCard.vue
에서 props
를 선언하여 전달 받았다.
이처럼, Props
는 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달할 때 쓰인다.
1-1처럼 문자열 배열을 사용하여 props
를 선언할 수도 있지만, 객체 문법을 사용하여 속성 타입과 함께 선언할 수도 있다.
//자식 컴포넌트 AppCard.vue
export default {
props: {
title: {
type: String,
default: 'news'
},
contents: {
type: String,
required: true
}
},
setup(){
return {}
}
}
데이터의 타입과 옵션을 설정해주므로 더욱 추천되는 방법이다.
type
: String
, Number
, ... 모든 기본 생성자 또는 모든 사용자 정의 타입이 올 수 있다. 배열을 이용하여 여러개의 타입을 선언할 수도 있다. (ex. [Number, String])defalut
: 속성값이 비어있거나 undefined
를 전달 받는 경우 기본값을 선언할 수 있다. required
: 속성이 필수값이라면 true
로 해서 설정할 수 있다.validator
: 속성값의 유효성 검사가 필요할 때 사용할 수 있다.❗ 컴포넌트 사용시 명시된 사항을 위반할 때 개발모드에서 콘솔 경고가 발생된다.
물론, 부모 컴포넌트에서도 데이터를 보낼때도 객체로 보낼 수 있다.
<template>
<AppCard :title="post.title" :contents="post.contents"></AppCard>
</template>
<script setup>
const title = ref("제목1");
const contents = ref("내용1");
</script>
v-bind:title="post.title" 를 간단히 :title="post.title" 로 쓸 수 있다.
모든
props
는 상위 속성과 하위 속성간에 단방향 바인딩으로 형성되어 있다.
상위 속성 업데이트 -> 하위 속성 업데이트 (o)
하위 속성 업데이트 -> 상위 속성 업데이트 (x)
이러한 성질은 하위 속성 변경 실수로 상위 속성을 변경하여 데이터 흐름을 이해하기 어렵게 만드는 것을 방지할 수 있다.
또한, 상위 컴포넌트가 업데이트될 때마다 하위 컴포넌트의 모든 props
의 최신 상태도 초기화 된다.
그렇기 때문에 자식 컴포넌트 내부에서 props
를 변경하지 않아야 한다.
//자식 컴포넌트 AppCard.vue
export default {
props: {
title: {
type: String,
default: 'news'
},
contents: {
type: String,
required: true
}
},
setup(props){
props.title = "제목2"; //불가능. 반영되지 않으며 콘솔창 경고 출력
return {}
}
}
부모로부터 전달받은 props.title을 자식 컴포넌트에서 변경할 수 없다.
그럼 다른 방법이 없을까?
그것은 자식컴포넌트에서 부모컴포넌트로 이벤트를 보내 부모컴포넌트에서 변경 이벤트를 발생시키는 것이다.
emits에 대해 알아보자.
emit
을 이용하면 자식 컴포넌트에서 부모 컴포넌트로 이벤트를 내보낼 수 있다.
이벤트를 내보낸다는게 도대체 무슨말이냐면,
(1) 부모 컴포넌트에서 전달받은 데이터 title
을 자식 컴포넌트에서 변경할 수 없다.
(2) 자식 컴포넌트에서 부모 컴포넌트로 이벤트를 보낼 수 있다. (emit
)
(3) 그렇다면 데이터 title
을 변경해야 되면, 자식 컴포넌트에서 부모 컴포넌트로 changeTitle
이벤트를 내보내겠다.
(4) changeTitle
이벤트를 받은 부모 컴포넌트는 해당 이벤트를 실행하여 title
을 변경한다.
한마디로, 자식 컴포넌트에서 변경할 수 없으니 이벤트를 부모로 보내서 부모에서 변경을 실행하겠다는 것이다.
사용법은 예시를 살펴보자.
//자식 컴포넌트 AppCard.vue
export default {
props: {
title: {
type: String,
default: 'news'
},
contents: {
type: String,
required: true
}
},
emits: ['changeTitle'],
setup(props, context){
const buttonEvent = () => {
context.emit('changeTitle');
}
return {buttonEvent}
}
}
props
와 마찬가지로 emits
를 선언해주어야 한다.
changeTitle
이벤트를 보냈다.
부모에서 이벤트를 받아보자.
<template>
<AppCard
:title="post.title"
:contents="post.contents"
@changeTitle = "post.title = '제목2'">
</AppCard>
</template>
자식에서 부모로 이벤트를 보낼 때, 부모는 @로 받는다.
부모에서 보낼 때는 : , 부모에서 받을 때는 @이다.
부모와 자식간의 데이터 전달은 단방향으로 이루어지는 것이다.
props
: 부모 -> 자식
emits
: 자식 -> 부모
Javascript 특성상 객체나 배열이 props로 전달되면 자식 컴포넌트에서는 prop 바인딩(값 변경)을 변경할 수 없지만 객체 또는 배열의 중첩 속성은 변경할 수 있다.
왜냐면, 많은 개발자들이 "어?? 되네??"하고 시도했다가 후에 데이터 흐름 추론이나 데이터 업데이트가 꼬여 골치아파지기 때문이다. (내 경험..)
중첩 속성은 변경할 수 있다는게 무슨 말이냐면,
자식 컴포넌트에서 props.title
은 변경할 수 없었지만, props.post.title
은 변경할 수 있다.
예시부터 보자.
위에서 처음에는 이렇게 보냈었다.
<!--부모 컴포넌트 -->
<template>
<AppCard
:title="post.title">
</AppCard>
</template>
그런데, 보낼 때 post
객체 자체를 보낸다고 생각해보자.
<!--부모 컴포넌트 -->
<template>
<AppCard
:post="post">
</AppCard>
</template>
<script>
export default {
setup(){
const post = reactive({
title: "제목"
});
return { post }
}
}
</script>
자식에서는 이렇게 받을 것이다.
//자식 컴포넌트 AppCard.vue
export default {
props: {
post: {
type: Object,
default: () => ({})
},
},
setup(props){
const buttonEvent = () => {
//props.title = "제목2"; 불가능
props.post.title = "제목2"; // 가능
}
return {buttonEvent}
}
}
props.title은 변경하지 못했는데, props.post.title은 변경할 수 있다. 객체 또는 배열의 중첩 속성은 변경할 수 있기 때문이다.
이것은 JavaScript에서 객체와 배열이 참조 타입(Reference Type)으로 전달되고 Vue가 이러한 변경을 방지하는것은 부당한 비용이 들기 때문에 가능하게 된 것이다.
이러한 변경은 데이터 흐름 추론에 어려움을 발생시키며, 부모에서 데이터를 업데이트 했을때 예상치 못한 문제가 발생할 수 있다.
(부모로부터 받은 데이터가 초기화만 되고, 업데이트 되지 않기를 바란다면 json.stringfy를 이용할 수 있다.)
Vue3 완전정복 - 짐코딩
Vue3 공식문서
팝업 호출, 컴포넌트 재사용 등에서 가장 중요한 props와 emits에 대한 내용이다.
script setup에서는 context가 아닌 defineProps, defineEmits를 사용한다.
이것에 대한 건 추후 script setup을 포스팅할 때 작성하도록 하겠다.
3번 내용 많은 도움 되었어요 감사합니다~