Frontend/Vue.js 시작하기 - Age of Vue.js

[ Vue.js 시작하기 ] 04. 컴포넌트 통신 방법 - 기본

둥둥 2022. 9. 26. 16:33
728x90
inflearn에서 수강 한 장기효(캡틴판교)님의 강의🔗정리입니다.

컴포넌트 통신

뷰 컴포넌트는 각각 고유한 데이터 유효 범위를 갖기때문에 각각의 컴포넌트는 데이터를 각각 관리한다.

따라서, 컴포넌트 간에 데이터를 주고 받기 위해 props 속성과 이벤트 발생이라는 전달방식을 이용해야 한다.

  • props 속성 : 상위에서 하위로는 데이터를 내려줌
  • 이벤트 발생 : 하위에서 상위로는 이벤트를 올려줌

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

N방향 통신

  1. AppHeader에서 특정 사용자가 로그인을 해서 LoginForm에 데이터 전달
  2. LoginForm에서 AppFooter로 입력받은 데이터를 전송
  3. ApppFooter에서 NavigationBar로 데이터 전송

이런 식으로 N방향 통신이 발생한다면 특정 컴포넌트의 변화에 대해 나머지 컴포넌트가 유기적으로 데이터를 주고 받았을때 데이터의 방향을 예측하기 어렵고, 그로 인한 버그를 추적하기 어렵다.

MVC패턴에서 이러한 문제가 많이 발생했다.

규칙이 생길 경우 데이터의 흐름을 추적하기가 쉽다.


props 속성

컴포넌트 통신에서 상위(부모 컴포넌트)에서 하위(자식 컴포넌트)로 데이터를 전달할때 사용되는 단방향 데이터 전달 방식이다.

부모 컴포넌트에서 자식 컴포넌트를 호출 시, 자식 컴포넌트 태그 내 v-bind:: 키워드를 통해 데이터를 전달하고, 자식 컴포넌트에서 props 객체를 통해 데이터를 전달받는 형식으로 이루어진다.

<app-header **v-bind:**프롭스 속성 이름="상위 컴포넌트의 데이터 이름(전달 데이터)"></app-header>
<app-header **:**프롭스 속성 이름="상위 컴포넌트의 데이터 이름(전달 데이터)"></app-header>

아래 코드에서 지역 컴포넌트에 data 속성을 이용해 message: ‘hi’를 주는데, 이 message: ‘hi’는 컴포넌트에서 관리하는 데이터이다.

이 데이터를 props 속성을 사용해 컴포넌트 아래있는 컴포넌트로 내려보자.

<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>',
             // 프롭스 속성 이름은 app-header 태그의 내용에 정의
             props: ['propsdata']        
        }

        new Vue({
            el: '#app',
            components: {
                'app-header': appHeader
            },
            data: {
                message: 'hi' // Root(상위) 컴포넌트에서 하위 컴포넌트로 내리고자하는 데이터
            }
        });

    </script>
</body>
  1. 자식(하위) 컴포넌트인 기준으로 부모(상위) 컴포넌트는 이고, 에서 내리고자하는 데이터의 이름인 message를 전달 데이터에 넣는다.
  2. props 속성이름은 이동하고자하는 하위 컴포넌트 태그의 내용에 정의한다.

위 코드의 경우 상위 컴포넌트의 message의 내용이 바뀌면 하위 컴포넌트의 데이터 또한 변경된다. (Reactivity가 props에도 반영이 됨)

💡 카멜 케이스
변수 네이밍 기법으로, 변수 명 생성 시 단어가 연결되어있을 때 두번째 단어에서 첫 자리를 대문자로 작성

데이터 바인딩 {{}} 을 사용하면 데이터 속성이나 프록스 속성을 넣어주면 값이 반영되기 때문에 데이터의 내용이 변경되는 것을 더 쉽게 확인할 수 있다.

<body>
    <div id="app">
        <!-- <app-header v-bind:프롭스 속성 이름="상위 컴포넌트의 데이터 이름"></app-header> -->
        <app-header v-bind:propsdata="message"></app-heade
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        var appHeader = { 
             //  template: '<h1>header</h1>',
             template: '<h1>**{{ propsdata }}**</h1>',  // 데이터 바인딩 : {{}}안에 데이터 속성이나 프록스 속성을 넣어주면 값이 뭐가 되었든간에 반영이 됨
             props: ['propsdata']    

        }

        new Vue({
            el: '#app',
            components: {
                'app-header': appHeader
            },
            data: {
                message: 'hi' 
            }
        });

    </script>
</body>

props 속성 실습

상위 컴포넌트인 컴포넌트에 새롭게 추가된 num: 10이라는 데이터를 새로운 하위 컴포넌트인 로 내려보자.

<body>
    <div id="app">
        <app-header v-bind:propsdata="message"></app-header>
        <app-content></app-content>
    </div>

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

        }

                // 새로운 하위 컴포넌트에 <Root>컴포넌트의 데이터 num: 10을 내려보자.
        var appContent = {
            template: '<div>content</div>',
            props: []
        }

        new Vue({
            el: '#app',
            components: {
                'app-header': appHeader,
                'app-content' : appContent
            },
            data: {
                message: 'hi', 
                num: 10
            }
        });

    </script>
</body>
</html>
나의 풀이
<body>
    <div id="app">
        <app-header v-bind:propsdata="message"></app-header>
        <app-content :props**N**um="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>content</div>',
            template: '<div>{{props**N**um}}</div>',
            props: ['props**N**um']
        }

        new Vue({
            el: '#app',
            components: {
                'app-header': appHeader,
                'app-content' : appContent
            },
            data: {
                message: 'hi', 
                num: 10
            }
        });

    </script>
</body>
🚨 Vue.js에서 Props undefined
화면에 10이 출력되지 않고, 개발자 도구를 열어보면 의 props에 있는 propsNum은 undefined가 출력되고, 그 아래의 $attrs의 propnum:10이 출력되는 상황이었다.

[ 원인 ]
props 속성 이름에 대소문자를 섞어 사용(camelCase)해 발생한 오류였다. 관련 가이드

기술적으로 props를 자식 구성 요소에 전달할 때에도 camelCase를 사용할 수 있으나 DOM 템플릿은 제외된다. 🔗
Vue 템플릿을 DOM에서 직접 작성하는 경우, Vue는 DOM에서 템플릿 문자열을 검색해야 하기때문에 몇가지 주의사항이 있다.
- 대소문자 구분 : HTML 태그와 속성 이름은 대소문자를 구분하지 않으므로 브라우저는 대문자를 소문자로 해석한다.
따라서 DOM 내 템플릿을 사용할 때 PascalCase 구성 요소 이름과 camelCased 소품이름 또는 v-on이벤트 이름은 모두 케밥대소문자(하이픈-으로 구분)을 사용해야한다.

[ 해결 ]
props 속성 이름을 소문자로 수정하였더니 원하는대로 출력되었다.
References. 🔗

▼ 수정 사항을 반영한 나의 코드

▼ 실습 정답

 

💡 Props의 이름 정의 시, 컴포넌트는 각각의 고유의 영역(유효범위)가 있기때문에 동일한 이름(propsdata)을 사용해도 위쪽에서 정의했던 내용과 구분이 된다.

이벤트 발생

컴포넌트의 통신 방법 중 하위 컴포넌트에서 상위 컴포넌트로 통신하는 방식.

하위 컴포넌트의 메서드, 라이프 사이클 훅과 같은 곳에 아래와 같이 코드 구현

// 하위 컴포넌트의 내용
this.$emit('이벤트 명');

해당 이벤트를 수신하기 위해 상위 컴포넌트의 템플릿에 아래의 코드 구현

<!-- 상위 컴포넌트의 템플릿 -->
<div id="app">
  <child-component v-on:이벤트 명="상위 컴포넌트의 실행할 메서드 명 또는 연산"></child-component>
</div>
event emit 발생

“click me”버튼 클릭 시 pass 이벤트가 발생하도록 구현해보았다.

<body>
    <div id="app">
        <app-header></app-header>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <!-- vue.js 스크립트 -->
    <script>
        var appHeader = {
            template: '<button v-on:click="passEvent">click me</button>', // 버튼 클릭시 이벤트 실행: v-on:click | 여기서는 버튼 클릭시 상위 컴포넌트로 이벤트를 보내려고함. -> passEvent라는 메서드 정의
            method: {  // 하위 컴포넌트의 메서드에 발생시킬 이벤트 추가
                passEvent: function(){  // 기본적으로 버튼을 클릭했을때 실행되는 함수(여기서는 passEvent를 클릭했을때 실행되는 함수)
                    this.$emit('pass'); // 'pass'라고 이벤트를 보냄
                }
            }
        }
        new Vue({
            el: '#app',
            components: {
                'app-header': appHeader
            }
        });
    </script>
</body>
  • 버튼 클릭 시 이벤트 실행 : v-on:click
  • 버튼 클릭시 상위 컴포넌트로 이벤트를 보내려고 하였기때문에 passEvent라는 메서드 정의하고 메서드에 passEvent메서드가 작동했을때 실행하는 함수를 만들어 그 안에 하위 컴포넌트의 내용을 추가하였다.

개발자 도구에서 확인 시 Timeline에서 event emit에 대한 로그(이력)들을 확인 할 수 있다.


event emit으로 콘솔 출력하기

event emit을 상위 컴포넌트 태그에서 받을수 있도록 해보자.

<body>
    <div id="app">
        <!-- <app-header v-on:하위컴포넌트에서 발생한 이벤트 이름="상위 컴포넌트의 메서드 이름"></app-header> -->
        <app-header v-on:pass="logText"></app-header>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <!-- vue.js 스크립트 -->
    <script>
        var appHeader = {
            template: '<button v-on:click="passEvent">click me</button>', // 버튼 클릭시 이벤트 실행: v-on:click | 여기서는 버튼 클릭시 상위 컴포넌트로 이벤트를 보내려고함.
            methods: {
                passEvent: function(){  // 기본적으로 버튼을 클릭했을때 실행되는 함수
                    this.$emit('pass'); // * 하위컴포넌트에서 발생된 이벤트 이름
                }
            }
        }

        new Vue({
            el: '#app',
            components: {
                'app-header': appHeader
            }, methods: {
                logText: function(){ // ** 상위 컴포넌트의 메서드 이름 
                    console.log('hi');
                }
            }
        });
    </script>
</body>

▼ 나의 이해

이벤트는 계속해서 추가되고 콘솔에는 ‘hi’가 계속해서 찍히는것을 확인할 수 있다.

 

🚨 Property or method "logText" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.

아래의 코드를 Live Server로 실행시킨 후 콘솔창을 확인했을때 발생한 오류이다.
이 블로그에 따르면 나의 경우에는 정의는 했는데, 이상한 자리에 넣은 경우에 해당했다.
(Component밑에 methods를 추가했어야했는데 methods밑에 methods를 추가)

하지만 코드의 자리를 수정한 경우에도 계속해서 해당 오류가 발생했다.
수업 코드와 내가 쓴 코드를 비교해본 결과 methods가 아니라 method로 작성된 것을 확인했다.
해당 부분을 수정하니 에러코드가 사라졌다.

▼ Error Code

 

▼ 해결

더보기

[ 에러가 발생한 오류 ]

  1. component 밑에 method를 썼어야 했는데 method 밑에 method를 써서 발생한 에러
  2. methods가 아닌 method로 작성되었음.

[ 수정된 코드]

두 부분을 모두 수정한 후로는 에러가 발생하지않았다.

 


event emit 실습

add라는 버튼을 눌렀을때 this.$emit을 이용해서 위쪽에 이벤트를 올려서 위에있는 데이터의 num의 값을 하나 증가시켜보자.

최종적 결과가 11이 나와야 하며, 클릭할때마다 증가하는 로직으로 구현해도 상관없다.

<body>
    <div id="app">
        <!-- <app-header v-on:하위컴포넌트에서 발생한 이벤트 이름="상위 컴포넌트의 메서드 이름"></app-header> -->
        <app-header v-on:pass="logText"></app-header>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <!-- vue.js 스크립트 -->
    <script>
        var appHeader = {
            template: '<button v-on:click="passEvent">click me</button>', 
            methods: {
                passEvent: function(){  
                    this.$emit('pass'); 
                }
            }
        }

        var appContent = {
            template: '<button v-on:click="addNumber">add</button>', // 버튼을 클릭하면 addNumber을 실행하겠다.
            methods: {
                addNumber: function(){
                    this.$emit();
                }
            }
        }

        new Vue({
            el: '#app',
            components: {
                'app-header': appHeader,
                'app-content': appContent
            }, methods: {
                logText: function(){
                    console.log('hi');
                }
                // ...
            }, 
            data: {
                num : 10
            }
        });
    </script>
</body>

 

나의 풀이

▼ code

최종값 11 출력은 성공했으나 클릭시마다 1씩 증가는 실패.

 

맞춰보기
<body>
    <div id="app">
        <!-- <app-header v-on:하위컴포넌트에서 발생한 이벤트 이름="상위 컴포넌트의 메서드 이름"></app-header> -->
        <app-header v-on:pass="logText"></app-header>
        <app-content v-on:add="logNum"></app-content>
        <p>{{ num }}</p> <!-- ** add클릭시 실시간으로 클릭수 올라가는것을 확인하기 위해 number을 표현-->
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> 
    <script>
        var appHeader = {
            template: '<button v-on:click="passEvent">click me</button>',
            methods: {
                passEvent: function(){  
                    this.$emit('pass');
                }
            }
        }

        var appContent = {
            template: '<button v-on:click="addNumber">add</button>', // 버튼을 클릭하면 addNumber을 실행하겠다.
            methods: {
                addNumber: function(){
                    this.$emit('add');
                }
            }
        }

        new Vue({
            el: '#app',
            components: {
                'app-header': appHeader,
                'app-content': appContent
            }, methods: {
                logText: function(){
                    console.log('hi');
                }, 
                logNum: function(){
                    this.num = this.num + 1; // 최종값 클릭 할때 마다 1씩 증가
                    console.log(this.num); // 실시간 반영이 잘 안돼서 log활용. * 바로 확인하기 위해선 number을 표현하면 됨
                }

                // ...
            }, 
            data: {
                num : 10
            }

        });
    </script>
</body>
  1. this를 사용해 data에 있는 num의 값을 가져오기
  2. 가져온 값 자신에 1을 더해 로직이 반복되면서 지속적으로 값이 증가되도록 구현
  3. Vue화면에서는 실시간으로 확인이 안되는점을 보완, 바로확인하기위해서 number을 데이터 바인딩을 이용해 {{ num }}으로 표현

뷰 인스턴스에서의 this

  • 개발자 도구 줄바꿈(개행) 단축키 shift + enter

getNumber함수에서 num에 접근하고 싶을때 this를 사용해서 접근한다.

객체 안에서 다른 속성을 가르킬때 this를 사용하면 this는 해당 객체(object)를 바라보게 된다.

메서드 함수 안에서 this가 해당 data에 있는 num이라는 객체를 가리킨다(바라보게 된다).

var vm = new Vue({});로 식을 작성하면 vm이 뭐가 되었든간에 new Vue는 vm에 담긴다.

var vm = new Vue({ // vm이 뭐가 되었던 간에 new Vue는 vm에 담김
            el: '#app',
            components: {
                'app-header': appHeader,
                'app-content': appContent
            }, methods: {
                logText: function(){
                    console.log('hi');
                }, 
                logNum: function(){
                    // N = this.num + 1; // 최종값 11 출력

                    this.num = this.num + 1; // 최종값 클릭 할때 마다 1씩 증가
                    console.log(this.num); // 실시간 반영이 잘 안돼서 log활용. 바로 확인하기 위해선 number을 표현하면 됨
                }

                // ...
            }, 
            data: {
                num : 10
            }

        });

콘솔창에 console.log(vm);을 입력시 Vue에 관한 내용이 찍히는것을 확인할 수 있다.

분명히 데이터 안에 선언했지만 바깥 레벨로 나와있는것이 뷰 내부의 동작(this)에 의해서 나왔다는것을 알 수 있다.

this 문법 정리

JavaScript this

 

728x90