vue 컴포넌트 통신
이 글은 doit vue.js 입문을 보고 그냥은 머리에 도저히 안들어와서 내용을 거의 옮겨적다시피 정리한 글이다...
이 글을 다시봐도 모르겠음 책 코드부분의 표시를 보면 그땐 이해가 잘될것같다.
컴포넌트 간 통신과 유효범위
앵귤러 1이나 백본과 같은 초기 자바스크립트 프레임워크에선 한 화면을 하나의view 로 간주했다. 따라서 한화면의 데이터를 해다 화면 영역 어디서든 호출할수 있었다.
하지만 뷰의경우 컴포넌트로 화면을 구성하므로, 같은 웹페이지라도 데이터를 공유할수가 없다. > 앞서 말했듯 컴포넌트 마다 자체적으로 고유한 유효범위(Scope)를 갖고있기 때문이다.
이는 뷰 프레임워크 내부적으로 정의된 특징이다. 따라서 각 컴포넌트의 유효범위가 독립적이기 때문에 다른 컴포넌트의 값을 직접적으로 참조할수가 없다.
<body>
<div id="app">
<h3>첫번째 인스턴스 영역</h3>
<my-global-component></my-global-component>
<my-local-component></my-local-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.2/dist/vue.js"></script>
<script>
var cmp1={
template:'<div>첫번째 지역 컴포넌트 : {{cmp1Data}}</di>',
data : function(){
return {
cmp1Data : 100
}
}
};
var cmp2 = {
template : '<div> 두번째 지역 컴포넌트 {{ cmp2Data}} </div>',
data: function(){
return {
cmp2Data : cmp1.data.cmp1Data
}
}
};
new Vue({
el : '#app',
//지역 컴포넌트 등록
components:{
'my-global-component': cmp1,
'my-local-component' : cmp2
}
})
</script>
</body>
var cmp2 에서 cmp2Data 를 cmp1의 data.cmp1Data를 참조하도록 했다. 보통 이렇게 참조가 되면 자바에서 처럼 100으로 참조값이 나와야하지만 {{ my-locla-compnent}} 에서는 아무것도 나타나지 않는다. 즉 my local-component 에서 my-global-component 값을 직접 참조할수 없기 때문이다.
뷰에서 미리정의해놓은 데이터 전달 방식에 따라 일관된 구조로 어플리케이션 작성이 가능해진다. 그러므로 개발자 개개인의 스타일대로 구성되지 않고, 어플리케이션이 모두 동일한 데이터의 흐름을 갖는다.
상하위 컴포넌트 관계
뷰 프레임워크에서 정의한 컴포넌트 데이터 전달방법을 따라야한다.
> 가장 기본적인 데이터 전달 방법은 상위(부모) - 하위(자식)컴포넌트 간의 데이터 전달 방법이다.
상위-하위 컴포넌트란 트리 구조에서 부모노드- 자식노드 처럼 컴포넌트 간의 관계가 부모, 자식으로 이루어진 컴포넌트를 의미한다.
지역 또는 전역으로 컴포넌트를 등록하면 등록된 컴포넌트는 자연스레 하위 컴포넌트(자식컴포넌트)가 된다. 그리고 하위 컴포넌트를 등록한 인스턴스는상위 컴포넌트가 된다.
상위에서 하위로 props 라는 속성을 전달한다. 하위에서 상위로는 기본적으로 이벤트만 전달할수있다.
상위에서 하위컴포넌트로 데이터 전달
props속성
props는 상위에서 하위로 데이터를 전달할때 사용하는 속성이다. props 속성을 사용하려면 하위 컴포넌트 속성에 정의한다.
Vue.component('child-component',{
props:['props속성 이름'],
});
그리고 상위 컴포넌트의 HTML코드에 등록된 child-component컴포넌트 태그에 v-bind속성을 추가한다.
<child-component v-bind:props 속성이름 = "상위 컴포넌트의 data속성></child-component>
v-bind 속성의 왼쪽 값으로 하위 컴포넌트에서 정의한 props 속성을 넣고, 오른쪽값으로 하위 컴포넌트에 전달할 상위 컴포넌트의 data속성을 지정한다.
상위 컴포넌트의 message 속성을 하위 컴포넌트에 props로 전달해서 메시지를 출력하는것이다.
new Vue()로 인스턴스를 하나 생성한다.
Vue.component()를 이용해 하위 컴포넌트인 child-component를 등록한다.
child-component 의 내용에 props속성으로 propsdata를 정의한다.
HTML 에 컴포넌트 태그를 추가한다. v-bind 속성에 v-bind : propsdata = 'message' 를 통해 상위 컴포넌트의 message 속성값인 Hello~~ 텍스트를 하위컴포넌트의 propsdata로 전달하였다.
child-component의 template속성에 정의된 <p> {{ propsdata }} </p> 에 Hello~~ 의텍스트가 들어간다.
<html>
<head>
<title></title>
</head>
<body>
<div id="app">
<!-- 4 HTML 에 컴포넌트 태그를 추가한다. v-bind 속성에 v-bind : propsdata = 'message' 를 통해
상위 컴포넌트의 message 속성값인 Hello~~ 텍스트를 하위컴포넌트의 propsdata로 전달하였다. -->
<child-component v-bind:propsdata="message"></child-component>
</div>
<script>
//2 Vue.component()를 이용해 하위 컴포넌트인 child-component를 등록한다.
Vue.component('child-component',{
//3 child-component 의 내용에 props속성으로 propsdata를 정의한다.
props : ['propsdata'],
template: '<p>{{propsdata}}</p>', // 5 child-component의 template속성에 정의된 <p> {{ propsdata }} </p> 에 Hello~~ 의텍스트가 들어간다.
});
new Vue({ //1 new Vue()로 인스턴스를 하나 생성한다.
el : '#app',
data:{
message : 'Hello Vue! passed from Parent Component'
}
});
</script>
</body>
</html>
여기서 child-component를 전역으로 등록한것 이외에 딱히 상위 컴포넌트를 지정하지않았는데도 뷰 인스턴스 안에 마치 상위컴포넌트가 있는것 처럼 하위 컴포넌트로 props를 내려보냈다. > 컴포넌트를 등록함과 동시에 뷰 인스턴스자체가 상위 인스턴스가 되기 때문이다.
뷰 인스턴스(상위 컴포넌트) ---props를 통해 데이터를 전달 --> child-component(하위컴포넌트)
이렇게 인스턴스에 새로운컴포넌트를 등록하면 기존에 있는 컴포넌트는 상위 컴포넌트(부모)가 되고 , 새로 등록된 컴포넌트는 하위(자식)컴포넌트가 된다. 이렇게 새 컴포넌트를 등록한 인스턴스를 최상위 컴포넌트 라고도 부른다.
하위에서 상위컴포넌트로 이벤트 전달
이벤트 발생과 수신
props는 상위에서 하위 컴포넌트로 데이터를 전달하는 방식이다.
하위에서 상위로의 통신은 event를 \emit 발생시켜 사우이 컴포넌트에 신호를 보낸다. 하위에 특정 이벤트가 발생하기를 상위에서 기다리다가 하위에 특정 이벤트가 생기면 사우이에서 해당 이벤틀를 수신해 상위 컴포넌트의 메소드를 호출한다.
이벤트 발생과 수신은 $emit() 과 v-on: 속성을 사용해 구현한다.
// 이벤트 살뱅
this.$emit('이벤트명');
// 이벤트 수신
<child-component v-on: 이벤트명="상위 컴포넌트의 메소드명"></child-component>
$emit()을 호출하면 (' 이벤트명' ) 에 정의된 이벤트가 발생한다. 일반적으로 $emit()을 호출하는 위츠는 하위 컴포넌트의 특정 메소드 내부이다. 그래서 this.$emit()의 this는 하위 컴포넌트를 가르킨다.
호출한 이벤트는 하위 컴포넌트를 등록하는 태그(상위 컴포넌트의 template속성에위치된 태그) 에서 v-on: 으로 받는다. 하위 컴포넌트에서 발생한 이벤트명을 v-on:속성에 지정하고, 속성의 값에 이벤트가 발생했을때 호출될 상위 컴포넌트의 메소드를 지정한다.
이부분은 좀 어렵고 순서가 헷갈려서 코드에 설명 써두었다. 책 68p
<body>
<div id="app">
<!-- 3. 하위 컴포넌트의 이벤트 명인 show-log , 상위 컴포넌트의 메소드 명 : printText -->
<!-- 3-2 2번에서 show-log이벤트가 발생해서 이 이벤트는 <child-component>에 정의한 v-on:show-log에 전달됨
v-on:show-log의 대상 메소드인 최상위 컴포넌트 메소드 printText()가 실행된다. -->
<child-component v-on:show-log="printText"></child-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.2/dist/vue.js"></script>
<script>
Vue.component('child-component', {
template: '<button v-on:click="showLog">show</button>',
// 1. 컴포넌트에 버튼 요소를 추가한다. 그럼 클릭하면 클릭 이벤트 v-on:click="showLog"에 따라서 showLog()메소드가 실행된다.
methods: {
showLog: function(){
this.$emit('show-log');
} //2. 메소드 안에서는 this.$emit('show-log')가 실행되면서 show-log이벤트가 발생한다.
}
});
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue ! passed from parent Component '
},
methods : { // 4. printText()는 recieve an event라는 로그를 출력하는 메소드로 결국 콘솔에 로그가 출력됨
printText: function(){
console.log("receive an event");
}
}
});
</script>
</body>
같은 레벨의 컴포넌트 간 통신
* 상위에서 하위 : props를 전달
* 하위에서 상위 : 이벤트로 전달
하위1 -> 상위 -> 하위 2 의 방식
뷰는상위에서 하위로만 데이터를 전달해야하는 기본적 통신규칙을 따르기 때문에 바로 옆 컴포넌트에 값을 전달하려면 하위에서 공통상위 컴포넌트로 이벤트를 전달하고 공통상위 컴포넌트에서 2개의 하위 컴포넌트에 props를 내려야한다.
> 컴포넌트 고유의 유효범위 때문에 다른 컴포넌트의 값을 직접 참조하지 못하므로 기본적인 데이터 전달 방식을 활용하여 같은 레벨간 통신이 되도록 구조를 갖춰야한다.
관계없는 컴포넌트 간 통신 : 이벤트 버스
이벤트 버스는 개발자가 지정한 두개의 컴포넌트 간에 데이터를 주고받을수 있는 방법이다. 컴포넌트 통신은 상위-하위 구조를 유지해야만 데이터를 주고 받을수 있었다. 그런데 이벤트 버스를 이용하면 이런 관계를 유지하지 않아도 데이터를
한 컴포넌트에서 다른 컴포넌트로 보낼수있다.
최하위 컴포넌트에서 상위 컴포넌트로 데이터를 보내려면 최하위 - 하위 - 상위- 뭐 이런식으로 서로 위에있는 공통상위를 타고 찾아가서 전달해야한다. 이게 좀 번거로우니 이때 이벤트 버스를 이용한다.
그럼 중간 컴포넌트를 거치지 않고 최하위 컴포넌트에서 상위 컴포넌트 로 바로 데이터를 전달할수있다.
이벤트 버스 형식
// 이벤트 버스를 위한 추가 인스턴스 1개 생성
var eventbus = new Vue();
// 이벤트를 보내는 컴포넌트
methods:{
메소드명 : function(){
eventBus.$emit('이벤트 명', 데이터);
}
}
// 이벤트를 받는 컴포넌트
methods:{
created : function(){
eventBus.$on('이벤트명', function(데이터){
.....
});
}
}
> 이벤트 버스를 구현하려면 기존의 어플리케이션 로직을 담는 인스턴스와 별개로 새로운 인스턴스를 1개 더 생성하고, 새 인스턴스를 이용해 이벤트를 보내고 받는다. 보내는 컴포넌트에서는 .$emit() 를 받는 컴포넌트에서는 .$on()을 사용해 구현한다.
<body>
<div id="app">
<child-component v-on:show-log="printText"></child-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.2/dist/vue.js"></script>
<script>
var eventBus = new Vue();
//1 이벤트 버스로 활용할 새 인스턴스 1개 생성하고 eventBus라는 변수에 참조한다. 그럼 eventBus의 변수로 새 인스턴스의 속성과 메소드에 접근할수있다.
Vue.component('child-component',{
//1-1 하위 컴포넌트에 template속성과 methods속성을 정의한다.template속성에 하위 컴포넌트 영역이라는 텍스트와 show버튼을 추가한다.
template:'<div>하위 컴포넌트 영역입니다<button v-on:click="showLog">show</button></div>',
//2 method 속성에 showLog()메소드를 정의하고 메소드 안에는 eventBus.$emit()를 선언하여 triggerEventBus라는 이벤트를 발생시키는 로직을 추가한다.
//3 이 이벤트를 발생할때 수신하는 쪽에 인자값으로 100이라는 숫자를 함께 전달한다.
methods: {
showLog: function(){
eventBus.$emit('triggerEventBus',100);
}
}
});
var app = new Vue({
el:'#app',
created: function(){
// 4 상위 컴포넌트의 created라이프사이클 훅에 eventBus.$on()으로 이벤트를 받는로직을 선언한다.
// 발생한 이벤트triggerEventBus를 수신할때 앞에서 전달한 인자값100이 콘솔에 출력된다.
eventBus.$on('triggerEventBus', function(value){
console.log("이벤트를 전달받음 전달받은값 : ", value);
});
}
});
</script>
</body>
참고서적 : Do it vue.js 입문