코드의 재사용을 위해 컴포넌트를 분리하면, 부모 컴포넌트와 자식 컴포넌트의 데이터 바인딩이 필요하다.
가장 간단한 방법은 바인딩하고 싶은 데이터를 v-model
로 바인딩 해주는 방법이다.
<!-- App.vue -->
<template>
<div>
<div>parent-compo {{number}}</div>
<child-compo v-model="number" />
</div>
</template>
<script>
import ChildCompo from "@/components/ChildCompo";
export default {
data () {
return {
number: 0
}
},
components: {
ChildCompo
}
}
</script>
부모 컴포넌트에서 v-model
로 number
라는 값을 내려주면, 자식 컴포넌트에서는 props
로 데이터를 접근할 수 있다.
<!-- ChildCompo.vue -->
<template>
<div>
<div>child-compo {{value}}</div>
<button @click="value += 1">Add 1</button>
</div>
</template>
<script>
export default {
props: {
value: Number
}
}
</script>
<script>
export default {
props: {
value: Number
}
}
</script>
단, v-model
로 바인딩 한 값은props
에서 value
로만 선언해야 한다.
v-model
을 사용하다보면 자식 컴포넌트에서 값을 바꾸어도 부모 컴포넌트에는 적용되지 않는 이슈
가 발생한다.
사실 위의 코드에서도 같은 문제가 발생한다.
아무리 클릭해도 자식 컴포넌트의 값 밖에 바뀌지 않는다.
props는 readonly인데, 자식 컴포넌트에서 값을 직접 바꿔서 발생하는 오류이다.
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "value"
사실 v-model
은 :value
와 @input
의 축약 문법이다. v-model
과 완전히 같은 코드이다.
:value
를 사용하여 number
변수를 바인딩해주고onChangeNumber
를 사용하여, 자식 컴포넌트에서 값 변경을 감지한다.this.$emit('input', 변경할 값)
으로 값이 변경되었음을 알려주어야 한다.<!-- App.vue -->
<template>
<div>
<div>parent-compo {{number}}</div>
<child-compo :value="number" @input="onChangeNumber" />
</div>
</template>
<script>
import ChildCompo from "@/components/ChildCompo";
export default {
data () {
return {
number: 0
}
},
methods: {
onChangeNumber (val) {
this.number = val;
}
},
components: {
ChildCompo
}
}
</script>
this.$emit('input', 변경할 값)
로 값이 변경되었음을 부모 컴포넌트에게 알려야 한다.'input'
은 부모 컴포넌트에서 @input
으로 선언한 키워드이다. (@
뒤에 오는 키워드)<!-- ChildCompo.vue -->
<template>
<div>
<div>child-compo {{value}}</div>
<button @click="onClickButton">Add 1</button>
</div>
</template>
<script>
export default {
props: {
value: Number
},
methods: {
onClickButton() {
this.$emit('input', this.value++) // 부모에게 값이 변경되었다는 것을 알림
}
}
}
</script>
위 코드를 동작시키면, 아까와 같은 warning이 발생한다.
다시 한 번 강조하지만,
props
는 readonly이다.
<!-- ChildCompo.vue -->
<template>
<div>
<div>child-compo {{value}}</div>
<button @click="onClickButton">Add 1</button>
</div>
</template>
<script>
export default {
props: {
value: Number
},
data () {
return {
pvalue: this.value
}
},
methods: {
onClickButton() {
this.$emit('input', this.pvalue++)
}
}
}
</script>
data
에서 변수를 하나 선언하여 this.value
를 복사하면 된다.
오류 없이 값이 함께 증가하는 것을 확인할 수 있다.
사실 :value
와 @input
이 아니어도 된다. 네이밍을 마음대로 해도 된다는 점을 강조하기 위해 극단적인 예시를 들어봤다.
<child-compo :hamster="number" @lovely="onChangeNumber" />
this.$emit('lovely', this.pvalue++)
예를 들어 자식 컴포넌트에서 어떤 버튼을 클릭했을 때, 부모 컴포넌트의 글자를 변경해주는 경우
와 같이 자식 컴포넌트에서 굳이 값을 내려주지 않아도 되는 경우가 있다. 그러면 @메서드이름
만 사용해도 된다.
<!-- App.vue -->
<template>
<div>
<div>{{title}}</div>
<child-compo @updateTitle="onChangeTitle" />
</div>
</template>
<script>
import ChildCompo from "@/components/ChildCompo";
export default {
data () {
return {
title: '클릭 전'
}
},
methods: {
onChangeTitle () {
this.title = '클릭했다'
}
},
components: {
ChildCompo
}
}
</script>
<!-- ChildCompo.vue -->
<template>
<div>
<button @click="onClickButton">Change Title</button>
</div>
</template>
<script>
export default {
methods: {
onClickButton() {
this.$emit('updateTitle')
}
}
}
</script>