5 컴포넌트 통신 방법 - 기본

source: categories/study/vue-beginner-lv1/vue-beginner5.md

5.1 컴포넌트 통신

이번 시간엔 컴포넌트 통신 방법에 대해서 알아보겠습니다.

Note

컴포넌트 통신 방식

뷰 컴포넌트는 각각 고유한 데이터 유효 범위를 갖습니다.
따라서, 컴포넌트 간에 데이터를 주고 받기 위해선 아래와 같은 규칙을 따라야 합니다.

  • 상위 컴포넌트에서 하위 컴포넌트로 통신하는 방법: props 전달
  • 하위 컴포넌트에서 상위 컴포넌트로 통신하는 방법: event 발생

앞에서 컴포넌트를 살펴봤을 때, 컴포넌트를 등록하면 관계가 생긴다고 말씀드렸었습니다.

Note

뷰 컴포넌트

컴포넌트는 화면의 영역을 구분하여 개발할 수 있는 뷰의 기능입니다.
컴포넌트 기반으로 화면을 개발하게되면 코드의 재사용성이 올라가고 빠르게 화면을 제작할 수 있습니다.

다시 그 부분을 보면, 화면에 따라서 영역별로 컴포넌트를 만들어 코드를 관리한다했습니다.

그런식으로 컴포넌트를 만들었을 때, 컴포너트간의 관계가 생깁니다.

위 이미지를 보시면 연회색으로 보이는 영역이 3개.

오른쪽 그림 보시면 page 1개에서 연회색 영역 3개로 나뉘어지고, 왼쪽아래 연회색 영역은 진회색 영역 2개로 나뉘어지고, 오른쪽 연회색 영역은 진회색 영역 3개로 나뉘어집니다.

이렇게 컴포넌트를 나눌 때마다 컴포넌트가 자연스럽게 하단에 위치하게됩니다. (하위, 자식 컴포넌트)

이렇게 관계가 생기는데, 이러한 관계의 중요한 점은,

Note

컴포넌트 통신 방식!!

바로 규칙이 생긴다는 점입니다.

컴포넌트는 각각 고유한 데이터 유효 범위를 갖습니다. 각 컴포넌트는 데이터를 각각 관리한다는 뜻입니다.

데이터를 상위 컴포넌트, 하위 컴포넌트간 공유하려면, 그 공유하기위한 방법으로 props라는 속성, 그리고 이벤트 전달방식을 이용해야됩니다.

그래서 다시 정리하면, 상위에서 하위로는 데이터를 내려주는 방식으로, (props 속성으로)

하위에서 상위로는 이벤트를 올려주는(이벤트 발생) 방식으로 서로 통신합니다.

5.2 컴포넌트 통신 규칙이 필요한 이유

앞서 작성했었던 컴포넌트 구조입니다.

app-headerapp-content, app-footer 컴포넌트를 등록해봤었습니다.

각각의 컴포넌트 하위에 컴포넌트를 하나씩 더 등록했다고 가정하겠습니다.

(NavigationBar, LoginForm, CompanyInfo)

여기서 app-header 컴포넌트에서 특정 사용자가 로그인을해서 LoginForm에 어떤 데이터를 전달했다고 해볼게요.

그리고 LoginForm에서 무언갈 처리하고나서 app-footer로 무언갈 보냈다고 할게요.

그랬더니 이번엔 app-footer에서 NavigationBar로 무언갈 보냈다고 해봅시다.

이렇게 특정 컴포넌트의 변화에 따라서 나머지 컴포넌트가 위와 같은 식으로 유기적으로 데이터를 주고받았을 때, 데이터의 방향을 예측하기 어렵다는 것입니다.

위와 같이 말도안되는 데이터의 관계가 생기기 시작하면, 특정 상태, 데이터가 바뀌었을 때 , 그로인한 버그를 추적하기가 어려운게 N방향 통신의 문제점입니다.

이전 MVC 패턴에서 이런 문제점이 많이 있었습니다.

그런데 이제는 데이터가 위와 같이 상위 컴포넌트에서 하위 컴포넌트, 즉, 아래로만 내려가게됩니다.

따라서 이러한 컴포넌트 통신방식이라는 규칙이 생겼을 때, 저희가 얻는 이점은, 처음엔 좀 불편하겠지만, 데이터의 흐름을 추적할 수 있다, 데이터는 항상 위에서 아래로 흐르고, 이벤트는 아래에서 위로 가기 때문에 흐름을 추적할 수 있다는 것입니다.

위와 같이 상위 컴포넌트에서 하위 컴포넌트로는 props가, 하위 컴포넌트에서 상위 컴포넌트로는 event가 흐릅니다.

5.3 props 속성

props에 대해 실습하기 위해서 playground/props.html 파일을 하나 생성합니다.

가독성을 위해 위와 같이 appHeader라는 변수생성.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5, user-scalable=no">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <app-header></app-header>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        var appHeader = {
            template: '<h1>header</h1>'
        }

        new Vue({
            el: '#app',
            components: {
                'app-header': appHeader,
            }
        })
    </script>
</body>
</html>

이제부터 props에 대해 알아보도록 하겠습니다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5, user-scalable=no">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <app-header></app-header>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        var appHeader = {
            template: '<h1>header</h1>'
        }

        new Vue({
            el: '#app',
            components: {
                'app-header': appHeader,
            },
            // 이제부터 props에 대해 알아보도록 하겠습니다.
            // 아래와 같이 data 속성을 활용해 message: 'hi'를 설정해보도록 하겠습니다.
            data: {
                message: 'hi'
            }
        })
    </script>
</body>
</html>

위와 같이 data 속성을 활용해 message: 'hi'를 설정해보도록 하겠습니다.

이렇게하면 Root 컴포넌트에 message: "hi"가 생기기 시작합니다.

이게 Root 컴포넌트에서 관리하는 data입니다.

dataapp-header 컴포넌트로 내릴겁니다.

내릴 때는 앞서 말씀드렸던 props라는 속성을 사용하시면 됩니다.

v-bind:프롭스 속성 이름="사우이 컴포넌트의 데이터 이름" 이것이 props 정의 문법입니다.

구조를 보시면 app-header의 상위 컴포넌트는 Root가 됩니다.

Rootdata의 이름은 message입니다.

즉, "상위 컴포넌트 데이터 이름" 부분에 "message"라고 넣습니다.

프롭스 속성 이름 부분은 위와 같이 appHeaderprops 키에 배열로 등록합니다.

그럼 위와 같이 propsdata라고 정의할 수 있습니다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5, user-scalable=no">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <!-- <app-header v-bind:프롭스 속성 이름="상위 컴포넌트의 데이터 이름"></app-header> -->
        <app-header v-bind:propsdata="message"></app-header>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        var appHeader = {
            template: '<h1>header</h1>',
            props: ['propsdata']
        }

        new Vue({
            el: '#app',
            components: {
                'app-header': appHeader,
            },
            // 이제부터 props에 대해 알아보도록 하겠습니다.
            // 아래와 같이 data 속성을 활용해 message: 'hi'를 설정해보도록 하겠습니다.
            data: {
                message: 'hi'
            }
        })
    </script>
</body>
</html>

보시면 app-header 컴포넌트에 propsdata 속성이름으로 "hi"라는 값이 내려왔습니다.

5.4 props 속성의 특징

저희가 컴포넌트 통신방법 두번째, 이벤트 발생에 대해 알아보기 전에 props에 대해 조금만 더 짚어보고 가겠습니다.

위와 같이 코드를 작성하시고 페이지를 보시면 app-headerprops가 생길겁니다.

여기서 알 수 있는 재밌는 사실은 Root 컴포넌트에 있는 message 있죠? 상위 컴포넌트에 있는 data 속성에 들어있는 것이 message인데, 여기에 들어있는 값이 바뀌었을 때,

위와 같이 hi hello로 바뀌었을 때,

바뀐 데이터가 그대로 app-header로 내려와서 반영됩니다.

이것이 바로 저희가 첫시간에 구현했었던, reactivity가 그대로 props에도 반영이 된다라는 사실을 알 수가 있습니다.

저희가 후반부에 배울 Vue.js의 기본적인 문법 중에 data binding이라는 것이 있는데,


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5, user-scalable=no">
    <title>Document</title>
</head>
<body>
<div id="app">
    <!-- <app-header v-bind:프롭스 속성 이름="상위 컴포넌트의 데이터 이름"></app-header> -->
    <app-header v-bind:propsdata="message"></app-header>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
    var appHeader = {
        template: '<h1>{{propsdata}}</h1>',
        props: ['propsdata']
    }

    new Vue({
        el: '#app',
        components: {
            'app-header': appHeader,
        },
        // 이제부터 props에 대해 알아보도록 하겠습니다.
        // 아래와 같이 data 속성을 활용해 message: 'hi'를 설정해보도록 하겠습니다.
        data: {
            message: 'hi'
        }
    })
</script>
</body>
</html>

위와 같이 mustache 문법을 통해


{{  }}

이렇게 넣어주고 그 안에 propsdata 변수를 넣어주면, propsdata 값이 무엇이 되었든간에 반영이되어 화면에 나타나게됩니다.

지금 기준으로는 'hi'를 내려보냈기 때문에 h1 태그 안에 hi가 출력될 것입니다.

확인해보시면 위와 같이 hi가 출력되는 것을 볼 수 있습니다.

Root 컴포넌트에서 messagehi를 보냈기 때문에 app-headerhi가 나타난 것입니다.

위와 같이 Root 컴포넌트에서 또 hi 대신에 hi props로 바꿔주면,

상위 컴포넌트의 data를 바꾸는 순간 하위 컴포넌트의 data가 바뀌면서

하위 컴포넌트의 props 속성에 반영되면서 화면이 다시 그려지는 것을 볼 수 있습니다.

5.5 실습 안내 - props 속성 실습

앞시간에 말씀드린 것처럼 이번시간엔 props 등록 실습을 해보겠습니다.

위 num이라는 datapropsappContent에 내려보내보시고 mustache(콧수염) 문법을 활용해서


{{  }}

num에 들어있는 값을 화면에 표현해보시길 바랍니다.

props를 위와 같이 등록하시고

numapp-content 컴포넌트에 v-bind를 활용해 내려보내면됩니다.


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5, user-scalable=no">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <!-- <app-header v-bind:프롭스 속성 이름="상위 컴포넌트의 데이터 이름"></app-header> -->
        <app-header v-bind:propsdata="message"></app-header>
        <app-content v-bind:propsdata="num"></app-content>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        var appHeader = {
            template: '<h1>{{propsdata}}</h1>',
            props: ['propsdata']
        }
        var appContent = {
            template: '<div>{{propsdata}}</div>',
            props: ['propsdata']
        }

        new Vue({
            el: '#app',
            components: {
                'app-header': appHeader,
                'app-content': appContent,
            },
            // 이제부터 props에 대해 알아보도록 하겠습니다.
            // 아래와 같이 data 속성을 활용해 message: 'hi'를 설정해보도록 하겠습니다.
            data: {
                message: 'hi',
                num: 10,
            }
        })
    </script>
</body>
</html>

5.6 실습 풀이 - props 속성 실습 풀이

위와 같이 propsdata라고 동일하게 쓰셔도 각 컴포넌트에 속해있는 것이기 때문에 유효범위가 각 컴포넌트로 제한되어있어서 아무 문제 없다.

그리고 컴포넌트 태그에 위와 같이 설정해준다.

위와 같이 작성하면 div 태그 안에 10이 출력될 것이다.

화면을 보시면 app-headerapp-content가 각각 propsdata를 가지고 있고,

Root 컴포넌트(인스턴스)의 data에서 num이라는 내용이 app-content로 내려왔다는 것을 알 수 있습니다.

여기서 다시 복습을 해보면, 위와 같이 Root 컴포넌트의 num 값이 바뀌었을 때, 그 값이 바뀐 상태로 아래 하위 컴포넌트로 전달이 될 것이고

전달이된 값이 화면에 바로 반영돼서 그려지는 것이 바로 Reactivity라고 말씀을 드렸습니다.

5.7 event emit

위에서 아래로는 data(props)를 전달하고, 아래서 위로는 data를 올리는 것이 아니라 event를 올립니다.

이것이 바로 event emit입니다.

이는 컴포넌트 통신의 흐름을 보다 더 파악하기 쉽도록 하게하기 위함입니다.

plaground/event-emit.html 파일을 만들어줍니다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5, user-scalable=no">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <app-header></app-header>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        var appHeader = {
            template: '<button>click me</button>'
        }

        new Vue({
            el: '#app',
            components: {
                'app-header': appHeader,
            }
        })
    </script>
</body>
</html>

위와 같이 컴포넌트를 작성해주시고 페이지를 확인해봅니다.

위와 같이 app-header 컴포넌트가 등록된 것을 볼 수 있습니다.

이제부터 event emit을 등록해보겠습니다.

위와 같이 Vue.js 문법 중에 하나인 v-on:click=""이라는 문법으로 클릭 이벤트를 등록할 수 있습니다.

이렇게 하면, 위 컴포넌트를 클릭했을 때, 위 컴포넌트의 상위 컴포넌트로 event를 보낼 수 있습니다.

즉, app-header를 클릭했을 때, Rootevent를 보낼 수 있습니다.

위와 같이 passEvent라는 메소드를 정의합니다.

그리고 method는 위와 같이 정의할 수 있습니다. method의 passEvent 프로퍼티에 해당 컴포넌트를 클릭했을 때 실행되는 콜백 함수를 정의할 수 있습니다.


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5, user-scalable=no">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <app-header></app-header>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        var appHeader = {
            // v-on 문법을 통해 event를 등록할 수 있습니다.
            // 이렇게하면, 해당 컴포넌트를 클릭했을 때, 해당 컴포넌트의 상위 컴포넌트로 event를 보낼 수 있습니다.
            // 아래와 같이 passEvent라는 메소드를 정의합니다.
            template: '<button v-on:click="passEvent">click me</button>',
            // 그리고 methods를 아래와 같이 정의합니다.
            // 이 컴포넌트를 클릭했을 때 passEvent 라는 콜백함수가 실행되는데, 그 passEvent 콜백함수를 여기서 정의합니다.
            methods: {
                passEvent: function () {
                    // this.$emit() 코드로 pass라는 이벤트를 한번 보내보도록 하겠습니다.
                    this.$emit('pass');
                }
            }
        }

        new Vue({
            el: '#app',
            components: {
                'app-header': appHeader,
            }
        })
    </script>
</body>
</html>

this.$emit('pass'); 코드로 pass라고 한번 이벤트를 보내보도록 하겠습니다.

vue 개발자도구를 보시면 Events 탭이 있습니다.

이 Events 탭에서 event emit에 대한 로그들을 확인할 수 있습니다.

app-header 컴포넌트를 클릭하시면 위와 같이 pass가 계속 발생이됩니다.

app-header에서 event가 발생이되는데 pass라고하는 event가 발생이 되었다는 뜻입니다.

여기까지 event 발생을 시켜봤구요, 이 다음 시간엔 이 event emit을 받아서 어떤 것들을 할 수 있는지 이어서 알아보도록 하겠습니다.

5.8 event emit으로 콘솔 출력하기

앞시간까지 this.$emit()이라는 API를 활용해서 pass라는 이벤트를 발생시켰습니다.

그래서 click했을 때 위와 같이 event가 발생하는 것을 볼 수 있습니다.

그런데 아직은 상위 컴포넌트에서 받을 수 있게 조작은 해주지 않은 상태입니다.

이를 위해선 app-header 컴포넌트에서 발생한 event를 잡아줘야됩니다.

문법은 위와 같습니다.

v-on:하위 컴포넌트에서 발생한 이벤트 이름="상위 컴포넌트의 메서드 이름"

app-header에서 pass라는 이벤트가 발생했었죠? (코드를 그렇게 작성함)

app-header의 상위 컴포넌트는 Root 인스턴스죠?

Root 인스턴스에서 실행할 method를 저희가 정의하고 그거를 돌려보면됩니다.

하위 컴포넌트에서 발생한 이벤트 이름은 pass입니다.

상위 컴포넌트의 메소드 이름은 위 부분에 작성을 해보겠습니다.

new Vue() 부분이 바로 Root 컴포넌트, 즉, app-header 컴포넌트의 상위 컴포넌트 입니다.


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5, user-scalable=no">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <!-- 하위 컴포넌트에서 발생한 이벤트를 상위 컴포넌트에서 받게해줘야합니다. -->
        <!-- <app-header v-on:하위 컴포넌트에서 발생한 이벤트 이름="상위 컴포넌트의 메소드 이름"></app-header> -->
        <!-- 하위 컴포넌트에서 발생한 메소드 이름은 pass 였습니다. this.$emit('pass') -->
        <app-header v-on:pass="logText"></app-header>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        var appHeader = {
            // v-on 문법을 통해 event를 등록할 수 있습니다.
            // 이렇게하면, 해당 컴포넌트를 클릭했을 때, 해당 컴포넌트의 상위 컴포넌트로 event를 보낼 수 있습니다.
            // 아래와 같이 passEvent라는 메소드를 정의합니다.
            template: '<button v-on:click="passEvent">click me</button>',
            // 그리고 methods를 아래와 같이 정의합니다.
            // 이 컴포넌트를 클릭했을 때 passEvent 라는 콜백함수가 실행되는데, 그 passEvent 콜백함수를 여기서 정의합니다.
            methods: {
                passEvent: function () {
                    // this.$emit() 코드로 pass라는 이벤트를 한번 보내보도록 하겠습니다.
                    this.$emit('pass');
                }
            }
        }

        new Vue({
            el: '#app',
            components: {
                'app-header': appHeader,
            },
            // app-header의 상위 컴포넌트는 바로 이 Root 컴포넌트(Vue 인스턴스)입니다.
            // 여기에 methods를 작성하겠습니다.
            methods: {
                logText: function () {
                    console.log('hi');
                }
            }
        })
    </script>
</body>
</html>

누를 때마다 event log는 계속 쌓이고 콘솔창에 hi는 계속 출력되는 것을 볼 수 있습니다.

이게 바로 event emit이고 하위 컴포넌트에서 상위 컴포넌트로 대화(통신)하는 방식입니다.

5.9 실습 안내 - event emit 실습 안내

new Vue()componentsapp-content 컴포넌트를 등록합니다.

이렇게 반복하시면서 코드나 문법에 익숙해지실 거 같습니다.

위와 같이 appContent 변수를 만들고 template 속성을 정의해줍니다.

appContent 컴포넌트를 클릭했을 때 실행할 함수 이름을 addNumber라고 정의해보겠습니다.

그럼 위와 같이 v-on:click="addNumber" 코드를 통해 appContent 컴포넌트를 클릭했을 때, addNumber 함수를 실행할 수 있습니다.

v-on이라고 하는 것은 Vue에서 제공하는 속성이고, 이러한 문법들은 나중에 문법에 대해 볼 때 더 자세하게 설명드리겠습니다.

여튼 위 appContent 컴포넌트를 클릭하면 addNumber 함수를 실행하겠다는 것.

위와 같이 this.$emit() 코드를 넣어줍니다.

여기서 직접 해보셔야될게 뭐냐면, 위 appContent 컴포넌트의 add 버튼을 클릭했을 때, addNumber 함수를 실행시키고, 실행된 addNumberthis.$emit() 코드를 통해 상위 컴포넌트에 event를 보내시고 (이벤트 이름은 this.$emit() 여기 소괄호 안에 작성해주면 된다), 그리고나서

다시 new Vue()data 속성을 정의하고 그 안에 num 속성을 정의합니다.

여튼 위와 같이 하위 컴포넌트에서 발생한 event를 상위 컴포넌트로 올려서 (app-content의 상위 컴포넌트는 Root(new Vue())) data.num 값을 1 증가시켜보세요.

그러니까 최종적으로 결과는 11이 되어야겠죠?

클릭할 때마다 12, 13, 14… 이렇게 증가하게 만드셔도됩니다.

여튼 num 값을 1을 증가시키는 event를 상위 컴포넌트로 올리고,

상위 컴포넌트의 methods에 정의해서 num에 접근하게끔 해보세요.

조금 힌트를 드리면 this.num으로.. methods 안에서 datanumthis.num으로 접근하실 수 있으니까, 참고하셔서 위의 methods 안에 메소드를 만들고 연결을 해보시면 되겠습니다.


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5, user-scalable=no">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <!-- 하위 컴포넌트에서 발생한 이벤트를 상위 컴포넌트에서 받게해줘야합니다. -->
        <!-- <app-header v-on:하위 컴포넌트에서 발생한 이벤트 이름="상위 컴포넌트의 메소드 이름"></app-header> -->
        <!-- 하위 컴포넌트에서 발생한 메소드 이름은 pass 였습니다. this.$emit('pass') -->
        <app-header v-on:pass="logText"></app-header>
        <app-content v-on:add="addNumber" v-bind:propsdata="num"></app-content>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        var appHeader = {
            // v-on 문법을 통해 event를 등록할 수 있습니다.
            // 이렇게하면, 해당 컴포넌트를 클릭했을 때, 해당 컴포넌트의 상위 컴포넌트로 event를 보낼 수 있습니다.
            // 아래와 같이 passEvent라는 메소드를 정의합니다.
            template: '<button v-on:click="passEvent">click me</button>',
            // 그리고 methods를 아래와 같이 정의합니다.
            // 이 컴포넌트를 클릭했을 때 passEvent 라는 콜백함수가 실행되는데, 그 passEvent 콜백함수를 여기서 정의합니다.
            methods: {
                passEvent: function () {
                    // this.$emit() 코드로 pass라는 이벤트를 한번 보내보도록 하겠습니다.
                    this.$emit('pass');
                }
            }
        }

        var appContent = {
            template: '<button v-on:click="addNumber">add {{propsdata}}</button>',
            methods: {
                addNumber: function () {
                    // 상위 컴포넌트에 이벤트를 올리는 방법, this.$emit();
                    this.$emit('add');
                }
            },
            props: ['propsdata']
        }

        new Vue({
            el: '#app',
            components: {
                'app-header': appHeader,
                'app-content': appContent,
            },
            // app-header의 상위 컴포넌트는 바로 이 Root 컴포넌트(Vue 인스턴스)입니다.
            // 여기에 methods를 작성하겠습니다.
            methods: {
                logText: function () {
                    console.log('hi');
                },
                addNumber: function () {
                    this.num++;
                }
            },
            data: {
                num: 10,
            }
        })
    </script>
</body>
</html>

5.10 실습 풀이

세번째 탭(이벤트 탭)

app-content라는 컴포넌트에서 increase라는 이벤트가 발생한 것을 확인할 수 있습니다.

그리고 위에 app-content 태그에 v-on:increase를 적어주시고(하위 컴포넌트에서 발생한 이벤트이름)

상위 컴포넌트의 메서드 이름까지 적어줍니다.

이제 상위 컴포넌트에서 event를 받아서 상위 컴포넌트의 increaseNumber 함수를 실행시켰으니, 여기서 num 값을 증가시키면됩니다.

add 버튼을 누를 때마다 위와 같이 num 값을 증가시키면됩니다.

위와 같이 add 버튼을 누를 때마다 num 값이 1씩 증가됩니다.

num을 화면상에 표현해봅시다.

p태그를 만드시고 num을


{{  }}

mustache 문법으로 넣어줍니다.

add 버튼을 누르면 increase가 나오면서 숫자가 올라가는 것을 볼 수 있습니다.

그리고 datanum 값도 반영되는 것을 확인할 수 있습니다.


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5, user-scalable=no">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <p>{{num}}</p>
        <!-- 하위 컴포넌트에서 발생한 이벤트를 상위 컴포넌트에서 받게해줘야합니다. -->
        <!-- <app-header v-on:하위 컴포넌트에서 발생한 이벤트 이름="상위 컴포넌트의 메소드 이름"></app-header> -->
        <!-- 하위 컴포넌트에서 발생한 메소드 이름은 pass 였습니다. this.$emit('pass') -->
        <app-header v-on:pass="logText"></app-header>
        <app-content v-on:add="addNumber" v-bind:propsdata="num"></app-content>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        var appHeader = {
            // v-on 문법을 통해 event를 등록할 수 있습니다.
            // 이렇게하면, 해당 컴포넌트를 클릭했을 때, 해당 컴포넌트의 상위 컴포넌트로 event를 보낼 수 있습니다.
            // 아래와 같이 passEvent라는 메소드를 정의합니다.
            template: '<button v-on:click="passEvent">click me</button>',
            // 그리고 methods를 아래와 같이 정의합니다.
            // 이 컴포넌트를 클릭했을 때 passEvent 라는 콜백함수가 실행되는데, 그 passEvent 콜백함수를 여기서 정의합니다.
            methods: {
                passEvent: function () {
                    // this.$emit() 코드로 pass라는 이벤트를 한번 보내보도록 하겠습니다.
                    this.$emit('pass');
                }
            }
        }

        var appContent = {
            template: '<button v-on:click="addNumber">add</button>',
            methods: {
                addNumber: function () {
                    // 상위 컴포넌트에 이벤트를 올리는 방법, this.$emit();
                    this.$emit('add');
                }
            },
            props: ['propsdata']
        }

        new Vue({
            el: '#app',
            components: {
                'app-header': appHeader,
                'app-content': appContent,
            },
            // app-header의 상위 컴포넌트는 바로 이 Root 컴포넌트(Vue 인스턴스)입니다.
            // 여기에 methods를 작성하겠습니다.
            methods: {
                logText: function () {
                    console.log('hi');
                },
                addNumber: function () {
                    this.num++;
                }
            },
            data: {
                num: 10,
            }
        })
    </script>
</body>
</html>

그런데 위 this에 대해서 좀 헷갈리실 분들이 계실 거 같습니다.

다음 시간에 이 this에 대해서 좀 더 설명드리고 위 코드들에 대해 이해하실 수 있게 코드 예시도 보여드리겠습니다.

5.11 Vue 인스턴스에서의 this


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5, user-scalable=no">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <p>{{num}}</p>
        <!-- 하위 컴포넌트에서 발생한 이벤트를 상위 컴포넌트에서 받게해줘야합니다. -->
        <!-- <app-header v-on:하위 컴포넌트에서 발생한 이벤트 이름="상위 컴포넌트의 메소드 이름"></app-header> -->
        <!-- 하위 컴포넌트에서 발생한 메소드 이름은 pass 였습니다. this.$emit('pass') -->
        <app-header v-on:pass="logText"></app-header>
        <app-content v-on:add="addNumber" v-bind:propsdata="num"></app-content>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        var appHeader = {
            // v-on 문법을 통해 event를 등록할 수 있습니다.
            // 이렇게하면, 해당 컴포넌트를 클릭했을 때, 해당 컴포넌트의 상위 컴포넌트로 event를 보낼 수 있습니다.
            // 아래와 같이 passEvent라는 메소드를 정의합니다.
            template: '<button v-on:click="passEvent">click me</button>',
            // 그리고 methods를 아래와 같이 정의합니다.
            // 이 컴포넌트를 클릭했을 때 passEvent 라는 콜백함수가 실행되는데, 그 passEvent 콜백함수를 여기서 정의합니다.
            methods: {
                passEvent: function () {
                    // this.$emit() 코드로 pass라는 이벤트를 한번 보내보도록 하겠습니다.
                    this.$emit('pass');
                }
            }
        }

        var appContent = {
            template: '<button v-on:click="addNumber">add</button>',
            methods: {
                addNumber: function () {
                    // 상위 컴포넌트에 이벤트를 올리는 방법, this.$emit();
                    this.$emit('add');
                }
            },
            props: ['propsdata']
        }

        new Vue({
            el: '#app',
            components: {
                'app-header': appHeader,
                'app-content': appContent,
            },
            // app-header의 상위 컴포넌트는 바로 이 Root 컴포넌트(Vue 인스턴스)입니다.
            // 여기에 methods를 작성하겠습니다.
            methods: {
                logText: function () {
                    console.log('hi');
                },
                addNumber: function () {
                    this.num++;
                }
            },
            data: {
                num: 10,
            }
        })
    </script>
</body>
</html>

위에서 보신 this에 대해 궁금하실텐데, 일단 이를 이해하기위해서 우선적으로 간단한 코드를 보겠습니다.

var obj = {
    num: 10,
    getNumber: function () {
        console.log(this.num);
    }
}
obj.getNumber(); // 10

객체 안에서 this를 사용하면 그 this는 해당 객체를 가리키게됩니다.

Vue 인스턴스 구조는 일단 자세하게 들여다보진 마세요. 예시일 뿐이니까 최대한 간단하게 작성한겁니다.

일단 이전에 말씀드렸던 관점에서 보면,

thisVue라는 객체를 가리킨다는 것은 이해하실 수 있으실겁니다.

물론 이 dataVue 내부적으로 new Vue()를 실행하면 기타 동작들을 거쳐서 조금 변환이 있겠지만, 기본적으로 methods 속성 안의 함수에서 thisVue 인스턴스를 가리키고, this.numVue 인스턴스datanum을 가리킨다고 보면됩니다.

// vue 인스턴스를 최대한 간소화해서 작성해봤습니다.
// 예시일 뿐이니까 최대한 간단하게 작성한겁니다.
// 아래 this가 Vue라는 객체를 가리킨다는 것은 이해하실 수 있으실겁니다.
// 물론 아래와 같은 상황에서 this가 Vue를 가리킬 순 없습니다.
// 실제 new Vue() 동작을 거쳐 아래와는 다른 모습으로, this가 Vue를 가리킬 수 있도록 객체 구조가 바뀔 것입니다.
// 여튼 기본적으로 methods 속성 안의 함수에서 this는 Vue 인스턴스를 가리키고, this.num은 Vue 인스턴스의 data의 num을 가리킨다고 보면됩니다.
var Vue = {
    el: '',
    num: 10,
    data: {
        num: 10,
    },
    methods: {
        getNumber: function () {
            this.num
        }
    }
}


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5, user-scalable=no">
    <title>Document</title>
</head>
<body>
<div id="app">
    <p>{{num}}</p>
    <!-- 하위 컴포넌트에서 발생한 이벤트를 상위 컴포넌트에서 받게해줘야합니다. -->
    <!-- <app-header v-on:하위 컴포넌트에서 발생한 이벤트 이름="상위 컴포넌트의 메소드 이름"></app-header> -->
    <!-- 하위 컴포넌트에서 발생한 메소드 이름은 pass 였습니다. this.$emit('pass') -->
    <app-header v-on:pass="logText"></app-header>
    <app-content v-on:add="addNumber" v-bind:propsdata="num"></app-content>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
    var appHeader = {
        // v-on 문법을 통해 event를 등록할 수 있습니다.
        // 이렇게하면, 해당 컴포넌트를 클릭했을 때, 해당 컴포넌트의 상위 컴포넌트로 event를 보낼 수 있습니다.
        // 아래와 같이 passEvent라는 메소드를 정의합니다.
        template: '<button v-on:click="passEvent">click me</button>',
        // 그리고 methods를 아래와 같이 정의합니다.
        // 이 컴포넌트를 클릭했을 때 passEvent 라는 콜백함수가 실행되는데, 그 passEvent 콜백함수를 여기서 정의합니다.
        methods: {
            passEvent: function () {
                // this.$emit() 코드로 pass라는 이벤트를 한번 보내보도록 하겠습니다.
                this.$emit('pass');
            }
        }
    }

    var appContent = {
        template: '<button v-on:click="addNumber">add</button>',
        methods: {
            addNumber: function () {
                // 상위 컴포넌트에 이벤트를 올리는 방법, this.$emit();
                this.$emit('add');
            }
        },
        props: ['propsdata']
    }

    var vm = new Vue({
        el: '#app',
        components: {
            'app-header': appHeader,
            'app-content': appContent,
        },
        // app-header의 상위 컴포넌트는 바로 이 Root 컴포넌트(Vue 인스턴스)입니다.
        // 여기에 methods를 작성하겠습니다.
        methods: {
            logText: function () {
                console.log('hi');
            },
            addNumber: function () {
                this.num++;
            }
        },
        data: {
            num: 10,
        }
    })
</script>
</body>
</html>

Vue 인스턴스vm이라는 변수에 할당합니다.

펼쳐서 조금 내리시면 num이라는게 바로 보입니다.

분명 저희는 data 안에 선언을 했지만 실제론 그 바깥 레벨로 나와있습니다.

Vue 인스턴스를 생성할 때 이러한 처리가 된다는 것을 추측할 수 있습니다.

따라서 this.numdatanum 속성을 가리킨다라고 보면 되겠습니다.