Props와 emit event를 활용하여 아래와 같이 데이터를 주고받는 Application을 완성하시오.

image

  • 데이터를 중복 선언하지 않는다.
  • 데이터를 선언한 곳에서만 데이터를 수정한다.
  • 컴포넌트들이 데이터를 공유한다면 공통 부모에 데이터를 저장하는 구조를 작성한다.

{%- raw -%} <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> {% endraw -%}
  • 필요한 요소들에 대해 App.vue 내부에 작성해준다.
  • data는 function의 return형으로 parentData와 childData로 나누어 정의한다.
  • 초기값은 null로 초기화한다.
  • 이 두 값은 각각 AppParent와 AppChild 컴포넌트로부터 받을 것이다.

AppParent.vue

{%- raw -%} <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> {% endraw -%}

AppChild.vue

{%- raw -%} <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> {% endraw -%}
  • App.vue는 AppParent를 자식 컴포넌트로 가진다.
  • AppParent는 AppChild를 자식 컴포넌트로 가진다.
  • 이를 적절하게 import하고 template을 작성해준다.
  • 주의할 점은 data는 최상위 컴포넌트인 App에서 한 번에 보관하므로 하위 컴포넌트에서는 이를 받는 props와 조작하는 method만 가지게 된다.
  • 스타일에 대한 정의는 이것으로 마치므로 이후에는 생략한다.

{%- raw -%} <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> {% endraw -%}
  • 하위 컴포넌트에 props로 전달해줄 appData를 data function에 추가한다.
  • 초기값은 null로 초기화한다.
  • App 컴포넌트의 input 값이 입력된다면(input) onInputChange 콜백함수를 호출한다.
  • 이 함수는 컴포넌트의 appData를 event.target.value로 할당한다.
  • 이를 자식 컴포넌트인 app-parent에 전달한다.

AppParent

{%- raw -%} <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> {% endraw -%}

AppChild

{%- raw -%} <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> {% endraw -%}
  • AppParent에서는 App으로부터 받은 appData props를 컴포넌트에서 사용한다.
  • 그와 동시에 자식 컴포넌트인 AppChild에게 props로 전달한다.
  • AppChild는 props로 appData를 String으로 저장하고, 이를 사용한다.

  • 이 일련의 컴포넌트들에서 데이터는 최상위 컴포넌트인 App 컴포넌트에만 저장한다.
  • 때문에 AppParent 컴포넌트에서 inputData를 바꾸더라도 이를 App 컴포넌트로 올려 App 컴포넌트의 parentData를 갱신해주어야 한다.

AppParent

{%- raw -%} <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> {% endraw -%}
  • input event에 대해 eventListener를 추가하고, onParentInputChange라는 콜백함수를 지정한다.
  • 이 콜백함수는 event의 target.value, 즉 input 태그 내의 입력값을 parent-input-change에 담아 App 컴포넌트로 emit한다.

App

{%- raw -%} <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> {% endraw -%}
  • AppParent로부터 @parent-input-change를 청취하다가 이벤트를 청취하면, onParentInputChange 콜백함수를 호출하도록 지정한다.
  • 이 콜백함수는 App 컴포넌트의 parentData를 AppParent가 전달한 inputData로 갱신하는 함수이다.
  • emit을 통해 이를 함께 전달하므로, inputData를 그대로 this.parentData로 지정한다.

  • 하위 컴포넌트에서 이 parentData를 사용해야 하므로, AppChild까지 내려보자.

App

{%- raw -%} ... <app-parent :app-data="appData" :parent-data="parentData" @parent-input-change="onParentInputChange" ></app-parent> ... {% endraw -%}

:parent-data를 통해 parentData를 props로 전달한다.


AppParent

{%- raw -%} props: { appData: String, parentData: String, } {% endraw -%}
{%- raw -%} ... <app-child :app-data="appData" :parent-data="parentData" ></app-child> ... {% endraw -%}
  • App 컴포넌트로부터 받은 parentData를 props에 지정하여 받는다.
  • 이를 다시 app-child 컴포넌트로 props로 전달한다.

AppChild

  • props에 parentData(String)을 추가한다.

  • AppParent와 같은 방식으로 App 컴포넌트까지 AppChild의 input의 값을 올려 갱신해보자.

AppChild

  • input 태그에 @input="onChildInputChange"를 추가한다.

  • input event에 대해 onChildInputChange를 호출한다.

  • methods에 다음의 함수를 정의한다.

{%- raw -%} methods: { onChildInputChange: function (event) { this.$emit('child-input-change', event.target.value) } }, {% endraw -%}
  • input 태그 내의 값에 대해 child-input-change라는 이벤트와 함께 emit으로 상위 컴포넌트인 AppParent로 전달한다.

AppParent

  • app-child 컴포넌트에 @child-input-change="onChildInputChange" 를 추가한다.
  • child-input-change 이벤트가 발생하면 정보와 함께 onChildInputChange를 호출한다.
  • methods에 다음의 함수를 정의한다.
{%- raw -%} methods: { ... onChildInputChange: function (inputData) { this.$emit('child-input-change', inputData) } }, {% endraw -%}
  • 마찬가지로 input 태그 내의 값에 대해 child-input-change라는 이벤트와 함께 emit으로 상위 컴포넌트인 App으로 전달한다.

App

  • app-parent 컴포넌트에 @child-input-change="onChildInputChange" 를 추가한다.
  • child-input-change 이벤트가 발생하면 정보와 함께 onChildInputChange를 호출한다.
  • methods에 다음의 함수를 정의한다.
{%- raw -%} methods: { ... onChildInputChange: function (inputData) { this.childData = inputData } }, {% endraw -%}
  • 최상위 App 컴포넌트이므로 childData를 가지고 있다.
  • AppChild부터 가져온 inputData를 비로소 갱신하는 용도로 사용한다.

  • childData를 갱신했으니 하위 컴포넌트로 이를 다시 내리는 일련의 작업이 필요하다.

App

{%- raw -%} <app-parent ... :child-data="childData" ... ></app-parent> {% endraw -%}
  • app-parent 컴포넌트에 :child-data="childData" 를 추가한다.
  • app-parent 컴포넌트에 childData를 props로 전달하는 것이다.

AppParent

{%- raw -%} props: { ... childData: String, } {% endraw -%}
  • props에 childData를 받아 String형으로 정의한다.
  • 이는 AppParent 내에서 interpolation으로 사용하게 된다.
  • 동시에 하위 컴포넌트인 AppChild로 내려주어야 한다.
  • 때문에 template의 app-child 컴포넌트에 :child-data="childData"를 추가한다.

AppChild

  • props에 childData를 받아 String형으로 정의한다.

  • input 태그 내에는 입력값만 있는 것이지 value 자체가 바뀌는 것이 아니다.

  • 때문에 input 태그 내에서 입력값에 대한 개별적인 갱신값을 value로 지정해 최신화해주어야 한다.

  • App의 input 태그 내에 :value="appData"를 추가한다.

  • AppParent의 input 태그 내에 :value="parentData"를 추가한다.

  • AppChild의 input 태그 내에 :value="childData"를 추가한다.


결과 화면

image

App

{%- raw -%} <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> {% endraw -%}

AppParent

{%- raw -%} <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> {% endraw -%}

AppChild

{%- raw -%} <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> {% endraw -%}