[Vue] Router props & emit 데이터 흐름 예시
작성:    
업데이트:
카테고리: Vue
[PRACTICE] Vue
문제
Props와 emit event를 활용하여 아래와 같이 데이터를 주고받는 Application을 완성하시오.
조건
- 데이터를 중복 선언하지 않는다.
- 데이터를 선언한 곳에서만 데이터를 수정한다.
- 컴포넌트들이 데이터를 공유한다면 공통 부모에 데이터를 저장하는 구조를 작성한다.
가. App.vue 편집
<template>
<div id="app">
<h1>App</h1>
<input type="text">
<p>parentData: {{ parentData }}</p>
<p>childData: {{ childData }}</p>
</div>
</template>
<script>
export default {
name: 'App',
components: {
},
data: function () {
return {
parentData: null,
childData: null
}
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
border: 1px solid black;
}
</style>
- 필요한 요소들에 대해 App.vue 내부에 작성해준다.
- data는 function의 return형으로 parentData와 childData로 나누어 정의한다.
- 초기값은 null로 초기화한다.
- 이 두 값은 각각 AppParent와 AppChild 컴포넌트로부터 받을 것이다.
나. 컴포넌트 생성 및 App.vue에 추가
AppParent.vue
<template>
<div id="appParent">
<h1>AppParent</h1>
<input type="text">
<p>appData: {{ appData }}</p>
<p>childData: {{ childData }}</p>
<app-child></app-child>
</div>
</template>
<script>
import AppChild from "./AppChild.vue"
export default {
name: "AppParent",
components: {
AppChild,
},
methods: {
}
}
</script>
<style>
#appParent {
border: 1px solid red;
margin: 1px;
}
</style>
AppChild.vue
<template>
<div id="appChild">
<h1>AppChild</h1>
<input type="text">
<p>appData: {{ appData }}</p>
<p>parentData: {{ parentData }}</p>
<p>childData: {{ childData }}</p>
</div>
</template>
<script>
export default {
name: "AppChild",
methods: {
}
}
</script>
<style>
#appChild {
border: 1px solid blue;
margin: 1px;
}
</style>
- App.vue는 AppParent를 자식 컴포넌트로 가진다.
- AppParent는 AppChild를 자식 컴포넌트로 가진다.
- 이를 적절하게 import하고 template을 작성해준다.
- 주의할 점은 data는 최상위 컴포넌트인 App에서 한 번에 보관하므로 하위 컴포넌트에서는 이를 받는 props와 조작하는 method만 가지게 된다.
- 스타일에 대한 정의는 이것으로 마치므로 이후에는 생략한다.
다. appData 내리기 1: App.vue
<template>
<div id="app">
<h1>App</h1>
<input type="text" @input="onInputChange">
<p>parentData: {{ parentData }}</p>
<p>childData: {{ childData }}</p>
<app-parent
:app-data="appData"
></app-parent>
</div>
</template>
<script>
import AppParent from "./components/AppParent.vue"
export default {
name: 'App',
components: {
AppParent,
},
data: function () {
return {
appData: null,
parentData: null,
childData: null
}
},
methods: {
onInputChange: function (event) {
this.appData = event.target.value
}
}
}
</script>
- 하위 컴포넌트에 props로 전달해줄 appData를 data function에 추가한다.
- 초기값은 null로 초기화한다.
- App 컴포넌트의 input 값이 입력된다면(input) onInputChange 콜백함수를 호출한다.
- 이 함수는 컴포넌트의 appData를 event.target.value로 할당한다.
- 이를 자식 컴포넌트인 app-parent에 전달한다.
라. appData 내리기 2: AppParent와 AppChild
AppParent
<template>
<div id="appParent">
<h1>AppParent</h1>
<input type="text">
<p>appData: {{ appData }}</p>
<p>childData: {{ childData }}</p>
<app-child
:app-data="appData"
></app-child>
</div>
</template>
<script>
import AppChild from "./AppChild.vue"
export default {
name: "AppParent",
components: {
AppChild,
},
methods: {
},
props: {
appData: String,
}
}
</script>
<style>
#appParent {
border: 1px solid red;
margin: 1px;
}
</style>
AppChild
<template>
<div id="appChild">
<h1>AppChild</h1>
<input type="text">
<p>appData: {{ appData }}</p>
<p>parentData: {{ parentData }}</p>
<p>childData: {{ childData }}</p>
</div>
</template>
<script>
export default {
name: "AppChild",
methods: {
},
props: {
appData: String,
}
}
</script>
- AppParent에서는 App으로부터 받은 appData props를 컴포넌트에서 사용한다.
- 그와 동시에 자식 컴포넌트인 AppChild에게 props로 전달한다.
- AppChild는 props로 appData를 String으로 저장하고, 이를 사용한다.
마. parentData 올리기
- 이 일련의 컴포넌트들에서 데이터는 최상위 컴포넌트인 App 컴포넌트에만 저장한다.
- 때문에 AppParent 컴포넌트에서 inputData를 바꾸더라도 이를 App 컴포넌트로 올려 App 컴포넌트의 parentData를 갱신해주어야 한다.
AppParent
<template>
<div id="appParent">
<h1>AppParent</h1>
<input type="text" @input="onParentInputChange">
<p>appData: {{ appData }}</p>
<p>childData: {{ childData }}</p>
<app-child
:app-data="appData"
></app-child>
</div>
</template>
<script>
import AppChild from "./AppChild.vue"
export default {
name: "AppParent",
components: {
AppChild,
},
methods: {
onParentInputChange: function (event) {
this.$emit('parent-input-change', event.target.value);
}
},
props: {
appData: String,
}
}
</script>
- input event에 대해 eventListener를 추가하고, onParentInputChange라는 콜백함수를 지정한다.
- 이 콜백함수는 event의 target.value, 즉 input 태그 내의 입력값을 parent-input-change에 담아 App 컴포넌트로 emit한다.
App
<template>
<div id="app">
<h1>App</h1>
<input type="text" @input="onInputChange">
<p>parentData: {{ parentData }}</p>
<p>childData: {{ childData }}</p>
<app-parent
:app-data="appData"
@parent-input-change="onParentInputChange"
></app-parent>
</div>
</template>
<script>
import AppParent from "./components/AppParent.vue"
export default {
name: 'App',
components: {
AppParent,
},
data: function () {
return {
appData: null,
parentData: null,
childData: null
}
},
methods: {
onInputChange: function (event) {
this.appData = event.target.value
},
onParentInputChange: function (inputData) {
this.parentData = inputData
}
}
}
</script>
- AppParent로부터 @parent-input-change를 청취하다가 이벤트를 청취하면, onParentInputChange 콜백함수를 호출하도록 지정한다.
- 이 콜백함수는 App 컴포넌트의 parentData를 AppParent가 전달한 inputData로 갱신하는 함수이다.
- emit을 통해 이를 함께 전달하므로, inputData를 그대로 this.parentData로 지정한다.
바. parentData 내리기
- 하위 컴포넌트에서 이 parentData를 사용해야 하므로, AppChild까지 내려보자.
App
...
<app-parent
:app-data="appData"
:parent-data="parentData"
@parent-input-change="onParentInputChange"
></app-parent>
...
:parent-data를 통해 parentData를 props로 전달한다.
AppParent
props: {
appData: String,
parentData: String,
}
...
<app-child
:app-data="appData"
:parent-data="parentData"
></app-child>
...
- App 컴포넌트로부터 받은 parentData를 props에 지정하여 받는다.
- 이를 다시 app-child 컴포넌트로 props로 전달한다.
AppChild
- props에 parentData(String)을 추가한다.
사. childData 올리기
- AppParent와 같은 방식으로 App 컴포넌트까지 AppChild의 input의 값을 올려 갱신해보자.
AppChild
- input 태그에
@input="onChildInputChange"
를 추가한다. -
input event에 대해 onChildInputChange를 호출한다.
- methods에 다음의 함수를 정의한다.
methods: {
onChildInputChange: function (event) {
this.$emit('child-input-change', event.target.value)
}
},
- input 태그 내의 값에 대해 child-input-change라는 이벤트와 함께 emit으로 상위 컴포넌트인 AppParent로 전달한다.
AppParent
app-child
컴포넌트에@child-input-change="onChildInputChange"
를 추가한다.- child-input-change 이벤트가 발생하면 정보와 함께 onChildInputChange를 호출한다.
- methods에 다음의 함수를 정의한다.
methods: {
...
onChildInputChange: function (inputData) {
this.$emit('child-input-change', inputData)
}
},
- 마찬가지로 input 태그 내의 값에 대해 child-input-change라는 이벤트와 함께 emit으로 상위 컴포넌트인 App으로 전달한다.
App
app-parent
컴포넌트에@child-input-change="onChildInputChange"
를 추가한다.- child-input-change 이벤트가 발생하면 정보와 함께 onChildInputChange를 호출한다.
- methods에 다음의 함수를 정의한다.
methods: {
...
onChildInputChange: function (inputData) {
this.childData = inputData
}
},
- 최상위 App 컴포넌트이므로 childData를 가지고 있다.
- AppChild부터 가져온 inputData를 비로소 갱신하는 용도로 사용한다.
아. childData 내리기
- childData를 갱신했으니 하위 컴포넌트로 이를 다시 내리는 일련의 작업이 필요하다.
App
<app-parent
...
:child-data="childData"
...
></app-parent>
app-parent
컴포넌트에:child-data="childData"
를 추가한다.- app-parent 컴포넌트에 childData를 props로 전달하는 것이다.
AppParent
props: {
...
childData: String,
}
- props에 childData를 받아 String형으로 정의한다.
- 이는 AppParent 내에서 interpolation으로 사용하게 된다.
- 동시에 하위 컴포넌트인 AppChild로 내려주어야 한다.
- 때문에 template의 app-child 컴포넌트에
:child-data="childData"
를 추가한다.
AppChild
- props에 childData를 받아 String형으로 정의한다.
자. input에 value 부여
- input 태그 내에는 입력값만 있는 것이지 value 자체가 바뀌는 것이 아니다.
-
때문에 input 태그 내에서 입력값에 대한 개별적인 갱신값을 value로 지정해 최신화해주어야 한다.
- App의 input 태그 내에
:value="appData"
를 추가한다. - AppParent의 input 태그 내에
:value="parentData"
를 추가한다. - AppChild의 input 태그 내에
:value="childData"
를 추가한다.
결과
결과 화면
App
<template>
<div id="app">
<h1>App</h1>
<input type="text" :value="appData" @input="onInputChange">
<p>parentData: {{ parentData }}</p>
<p>childData: {{ childData }}</p>
<app-parent
:app-data="appData"
:parent-data="parentData"
:child-data="childData"
@parent-input-change="onParentInputChange"
@child-input-change="onChildInputChange"
></app-parent>
</div>
</template>
<script>
import AppParent from "./components/AppParent.vue"
export default {
name: 'App',
components: {
AppParent,
},
data: function () {
return {
appData: null,
parentData: null,
childData: null
}
},
methods: {
onInputChange: function (event) {
this.appData = event.target.value
},
onParentInputChange: function (inputData) {
this.parentData = inputData
},
onChildInputChange: function (inputData) {
this.childData = inputData
}
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
border: 1px solid black;
}
</style>
AppParent
<template>
<div id="appParent">
<h1>AppParent</h1>
<input type="text" :value="parentData" @input="onParentInputChange">
<p>appData: {{ appData }}</p>
<p>childData: {{ childData }}</p>
<app-child
:app-data="appData"
:parent-data="parentData"
:child-data="childData"
@child-input-change="onChildInputChange"
></app-child>
</div>
</template>
<script>
import AppChild from "./AppChild.vue"
export default {
name: "AppParent",
components: {
AppChild,
},
methods: {
onParentInputChange: function (event) {
this.$emit('parent-input-change', event.target.value);
},
onChildInputChange: function (inputData) {
this.$emit('child-input-change', inputData)
}
},
props: {
appData: String,
parentData: String,
childData: String,
}
}
</script>
<style>
#appParent {
border: 1px solid red;
margin: 1px;
}
</style>
AppChild
<template>
<div id="appChild">
<h1>AppChild</h1>
<input type="text" :value="childData" @input="onChildInputChange">
<p>appData: {{ appData }}</p>
<p>parentData: {{ parentData }}</p>
<p>childData: {{ childData }}</p>
</div>
</template>
<script>
export default {
name: "AppChild",
methods: {
onChildInputChange: function (event) {
this.$emit('child-input-change', event.target.value)
}
},
props: {
appData: String,
parentData: String,
childData: String,
}
}
</script>
<style>
#appChild {
border: 1px solid blue;
margin: 1px;
}
</style>
댓글남기기