4 minute read


1. 뷰 컴포넌트 통신

이전 글인 ‘컴포넌트란?’에서 정리한 것처럼 컴포넌트는 유효 범위를 갖고 있기 때문에 같은 웹 페이지라도 데이터를 공유할 수 없다. 하지만 이러한 문제를 해결하기 위해 뷰에서는 자체적으로 정의해둔 컴포넌트간의 통신방법이 있다.

그중 가장 기본이 되는 방법은 상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달하는 방법이다. 상위 - 하위 컴포넌트의 관계는 흔히 말하는 트리구조를 떠올리면 이해하기 쉽다.



뷰는 단방향 데이터 흐름을 기본으로 하고 있기 때문에 하위 컴포넌트에서 상위 컴포넌트로 데이터를 전달하는 방법을 지원하지 않습니다.

따라서, 위 이미지와 같이 상위 컴포넌트는 하위 컴포넌트에게 props 속성을 이용해서 데이터를 전달하고, 하위 컴포넌트는 상위 컴포넌트로 이벤트를 전달해서 통신을 할 수 있습니다.



2. 상위 -> 하위 컴포넌트 데이터 전달

상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달할 때는 아래와 같이 하위 컴포넌트에 props 속성을 정의합니다.

1
2
3
Vue.component('child-component', {
    props: ['props 속성이름'],
});

그리고 child-component에 v-bind 속성을 추가합니다.

1
<child-component v-bind:props 속성 이름="상위 컴포넌트의 data 속성"></child-component>

전체적인 코드를 보면 아래와 같습니다.


뷰 인스턴스의 data 속성에 정의된 message 속성을 하위 컴포넌트에 props로 전달하여 화면에 보여준다.

또 알아야 할 한가지는 child-component를 전역 컴포넌트로 등록을 했고, 별다른 상위 컴포넌트가 없었지만 전역 컴포넌트를 등록함과 동시에 인스턴스가 상위 컴포넌트가 됐기 때문에 props를 정상적으로 내려보낼 수 있다는 것이다.



3. 하위 -> 상위 컴포넌트 데이터 전달

하위 컴포넌트에서 상위 컴포넌트로 데이터를 전달할 때는 이벤트를 발생시켜 상위 컴포넌트로 신호를 보내면 된다. 상위 컴포넌트에서 하위 컴포넌트의 이벤트가 발생하기를 기다리다가 이벤트가 발생했을 때 상위 컴포넌트가 해당 이벤트를 수신하여 메서드를 호출하는 방식이다.


3.1 이벤트 발생과 수신 형식

이벤트의 발생과 수신은 $emit(), v-on: 속성을 사용한다.

1
this.$emit('이벤트명');
1
<child-component v-on:이벤트명="상위 컴포넌트의 메서드명"></child-component>


3.2 이벤트 발생과 수신 흐름

$emit()은 보통 하위 텀포넌트의 특정 메서드 내부에서 호출되며, 호출하면 괄호 안에 정의된 이벤트가 발생한다. 호출한 이벤트는 하위 컴포넌트를 등록하는 태그에서 v-on: 으로 받는다.

아래 샘플 코드에서는 에서 v-on:show-log="printText" 부분을 보면 전역으로 선언한 컴포넌트의 showLog 메서드 명을 v-on: 속성에 지정하고 속성 값에 이벤트가 발생 했을 때 호출될 상위 메서드 즉, 인스턴스의 메서드 명을 지정한다.

전체적인 순서를 보면 컴포넌트의 버튼을 클릭하면 해당 컴포넌트의 메서드인 showLog가 호출이 되고 이벤트가 발생한다. 이벤트가 발행하면 v-on: 속성에 정의한 show-log에 전달되고 상위 컴포넌트의 메서드인 printText가 실행되는 순서로 흘러간다.


위와 같은 방식으로 하위 컴포넌트에서 상위 컴포넌트로 메서드를 실행하거나 하위 컴포넌트로 내려보내는 props의 값을 조정할 수도 있다.



4. 이벤트 버스

앞서 말했듯 뷰는 단방향 데이터 흐름을 갖고 있기 때문에 상위 컴포넌트에서 하위 컴포넌트로 데이터 전달이 가능하고, 하위 컴포넌트에서는 상위 컴포넌트로 이벤트를 발생시켜 데이터를 전달할 수 있다고 설명했다. 하지만 이와 같은 방법은 하위 컴포넌트가 많아지면 비효율적인 방법이다. 그래서 뷰는 상위 컴포넌트를 거치지 않고 다른 컴포넌트와 데이터를 주고 받기 위해 이벤트 버스라는 기능을 제공한다.

이벤트 버스는 개발자가 지정한 2개의 컴포넌트간의 데이터를 주고 받을 수 있는 방법으로 좀 더 편리하게 데이터를 전달할 수 있다. 형식은 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//이벤트 버스를 사용하기 위한 인스턴스 생성
var eventBus = new Vue();

//이벤트를 보내는 컴포넌트
methods : {
  메서드  : function(){
    eventBus.$emit('이벤트 명', '데이터');
  }
};

//이벤트를 받는 컴포넌트
methods : {
  created : function(){
    eventBus.$on('이벤트 명', function(데이터)){
        cosole.log(데이터);
    });
  }
}


이벤트 버스로 사용하기 위한 인스턴스를 생성 후 이벤트를 보낼 컴포넌트에서 $emit을 사용하여 메서드를 정의하고 이벤트를 받을 컴포넌트에서는 $on을 사용하여 메서드를 정의한다. 아래 샘플 코드를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<div id="app">
    <child-component1></child-component1>
    <child-component2></child-component2>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2.5.2/dist/vue.js"></script>

<script>
    var eventBus = new Vue();

    Vue.component('child-component1', {
        template:'<div>이벤트버스<button v-on:click="sendData">show</button></div>',
        methods: {
            sendData: function() {
                eventBus.$emit('eventBusTest', 100);
            }
        },
    });

    Vue.component('child-component2', {
        template: '<h1>Heoll Vue!!</h1>',
        mounted : function(){
            eventBus.$on('eventBusTest', function(value){
                console.log("이벤트를 전달 받음. 전달 받은 값 : ", value);
                document.querySelector('h1').innerHTML = value;
            });
        },
    });

    var app = new Vue({
        el : '#app',
    });
    
</script>

상위 컴포넌트인 인스턴스를 거치치 않고 전역으로 선언된 2개의 컴포넌트끼리 데이터를 주고 받는 코드이다.

child-component1 에서 $emit을 사용하여 ‘eventBusTest’라는 이벤트 명으로 100을 전달하는 메서드를 생성하고, child-component2 에서 $on을 사용하여 ‘eventBusTest’라는 이름으로 이벤트를 전달 받고 value에 100을 내려받는다.

버튼을 누르면 Hello Vue!! 텍스트가 100으로 바뀌는 것을 볼 수 있다.



5. SPC 체계에서 데이터 주고받기

SPC(Single Page Component) 체계는 Vue에서 채택하고 있는 하나의 페이지로 컴포넌트를 관리하는 방식이다.

SPC의 템플릿 형식에서 데이터를 주고 받을 때는 어떻게 해야 하는지 알아보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//main.vue
<template>
  <div>
    <button @click = "goData" >데이터 넘기기</button>
  <div>
  <child-component v-if="openActive" @close="openActive = false, testData = 0" :sendData="testData"></child-component>
</template>

<sciprt>

export default(){
  component: { childComponent}
  data(){
    return{
      testData : 0,
      openActive : false,
    }
  },
  methods(){
    goData(){
      this.openActive = true;
      this.testData += 1;
    }
  }
}
</scrip>

<style>
<style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//childComponent.vue
<template>
  <div class="childMain">
		<div>child component 영역</div><br/>

		<div>전달받은 데이터 : </div>
		<button @click = "$emit('close')">부모로 이벤트 전달</button>
	</div>
</template>

<sciprt>
export default(){
  data(){
    return{

    }
  },
  props:{
    sendData : Number,
  },
  mounted(){
    console.log(this.sendData);
  }
}
</scrip>

<style>
<style>

main.vue 에서 childComponent로 데이터를 전달하는 코드이다. ‘데이터 넘기기’ 버튼을 클릭하면 v-if 조건에 걸린 openActive가 true로 변경되며 sendData라는 이름으로 testData가 childComponent로 전달된다.

childComponent에서는 props로 sendData를 받아 변수로 사용하게 되고 넘겨받은 데이터의 type을 지정한다.
‘데이터 넘기기’ 버튼을 클릭할 때마다 testData는 1씩 증가하게 되고 childComponent로 전달되며 화면에 표시된다.

반대로 childComponent에서는 ‘부모로 이벤트 전달’ 버튼을 클릭하면 $emit을 통해 부모인 main.vue로 close를 전달하면 부모 컴포넌트에서 @close가 실행되며 testData를 0으로 초기화 하며 openActive가 false로 변하게 된다.