꾸준한 개발일기
Vue.js :: 형제 컴포넌트 간 데이터 전달 방법(event.$emit, eventBus.$on, eventBus.$off) 본문
Vue.js :: 형제 컴포넌트 간 데이터 전달 방법(event.$emit, eventBus.$on, eventBus.$off)
꾸개일 2022. 8. 1. 14:42form 방식이나 ajax로 구현한 게시판 crud에 vue.js와 axios 비동기 통신을 적용해보았다.
Vue.js를 사용하면서 편의를 위해 SPA로 작성하고, 페이지 전환은 보통 라우터를 사용해야 하지만
내가 속해있는 프로젝트에서는 전자정부프레임워크 spring의 jsp를 사용하기 때문에
이 환경에 맞춰 만들어보려했다.
v-show를 이용하여 페이지 전환을 하며 컴포넌트 별 화면을 껐다 켰다 해주었는데
그 중 메인 포인트는 컴포넌트 간의 데이터 통신이었다.
컴포넌트로 화면을 구성하게 되면 같은 웹페이지라도 데이터를 공유할 수 없다.
컴포넌트마다 고유한 유효 범위를 갖기 때문이다.
그렇다면 데이터 전달을 하려면 어떻게 해야할까?
형제 컴포넌트 간의 데이터 전달 방법
Vue.js에는 이벤트버스 라는 것이 있다.
독립적인 컴포넌트끼리 이벤트를 통신해야할 때 단순히 EventBus를 활용해서 간단하게 처리할 수 있다.
부모 - 자식 컴포넌트가 아닌 관계의 컴포넌트끼리 데이터 통신 예제를 진행하기 위해서
아래와 같은 컴포넌트 트리 구조를 만들었다.
Root Instance (instance.js)
└─ 최상위 컴포넌트 (App.vue)
├─ Read Component
├─ Create Component
├─ Update Component
=> 현재 컴포넌트 트리
Event Bus
관계없는 Read Component와 Update Component 간 eventBus를 사용하여 버튼을 클릭하면
user 객체를 전달하는 예제를 진행해보겠다.
instance.js
import readComponent from './read.js';
import createComponent from './create.js';
import updateComponent from './update.js';
export const eventBus = new Vue();
const read_component = readComponent;
const create_component = createComponent;
const update_component = updateComponent;
new Vue({
el: '#app',
components: {
'read-component': read_component,
'create-component': create_component,
'update-component': update_component
},
data() {
return {
isShow: 'read',
}
},
})
먼저 Root에서 export const type으로 eventBus를 등록하면 다른 컴포넌트에서
import 를 하여 eventBus를 사용할 수 있다.
read.js (Read Component)
import { eventBus } from './instance.js';
export default {
template : `
<div class="col-md-6 themed-grid-col" v-show="$parent.isShow == 'read'">
<h1>Users List</h1>
<button class="btn btn-outline-primary"><a @click="showCreatePage">Add</a></button>
<div>* email을 클릭하면 상세페이지로 이동</div>
<table class="table">
<thead>
<tr>
<th scope="col">id</th>
<th scope="col">first_name</th>
<th scope="col">last_name</th>
<th scope="col">email</th>
</tr>
</thead>
<tbody>
<tr v-for="item in users">
<td scope="row">{{ item.id }}</td>
<td><a @click="showUpdatePage(item)">{{ item.firstName }}</a></td>
<td><a @click="showUpdatePage(item)">{{ item.lastName }}</a></td>
<td><a @click="showUpdatePage(item)">{{ item.email }}</a></td>
</tr>
</tbody>
</table>
</div>
`,
data() {
return {
users: [],
user : {
id: ''
}
}
},
mounted(){
axios.get("/model/read").then((response)=>{
this.users = response.data;
}).catch((error)=>{
console.log(error);
})
},
methods : {
showCreatePage : function(){
this.$parent.isShow = 'create'
},
showUpdatePage : function(user){
eventBus.$emit("showUpdatePage", user);
this.$parent.isShow = 'update';
},
}
}
버튼을 클릭하면 @click으로 인하여 methods 단에 있는 showUpdatePage를 호출한다.
그리고 그 안에 있는 event.$emit 로 이벤트를 보내게 된다.
첫번째 parameter에 있는 것은 이벤트의 이름이다. 위의 예제에서 함수와 이름이 같지만 달라도 상관없다.
두번째 parameter에 있는 것은 그 이벤트에 같이 보낼 값이다. 물론 값을 보내지 않아도 될 경우에는 보내지 않아도
상관은 없다.
read.js (Read Component)
import { eventBus } from "./instance.js";
export default {
template : `
<div class="col-md-6 themed-grid-col" v-show="$parent.isShow == 'update'">
<h1>Update User</h1>
<form>
<div class="mb-3">
<label for="id" class="form-label">Id</label>
<input type="text" class="form-control" v-model="users.id" readonly="readonly">
</div>
<div class="mb-3">
<label for="firstName" class="form-label">First Name</label>
<input type="text" class="form-control" v-model="users.firstName">
</div>
<div class="mb-3">
<label for="lastName" class="form-label">Last Name</label>
<input type="text" class="form-control" v-model="users.lastName">
</div>
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<input type="text" class="form-control" v-model="users.email">
</div>
<div class="mb-3">
<label for="gender" class="form-label">Gender</label>
<select v-model="users.gender" class="form-select" aria-label="Default select example">
<option value="Male">Male</option>
<option value="Female">Female</option>
</select>
</div>
<div class="mb-3">
<label for="ipAddress" class="form-label">IP Address</label>
<input type="text" class="form-control" v-model="users.ipAddress">
</div>
<div class="mb-3">
<label for="lastUpdate" class="form-label">Last Update</label>
<input type="text" class="form-control" v-model="users.lastUpdate" readonly="readonly">
</div>
<button type="button" @click="update" class="btn btn-outline-primary">Update</button>
<button type="button" id="btn-delete" class="btn btn-outline-primary">Delete</button>
<button class="btn btn-outline-primary"><a href="/model/">List</a></button>
</form>
</div>
`,
data() {
return {
users: {
id: '',
firstName: '',
lastName: '',
email: '',
gender: '',
ipAddress: '',
},
}
},
created() {
eventBus.$on("showUpdatePage", (user) => {
this.users = user;
});
},
beforeDestroy() {
eventBus.$off("showUpdatePage");
},
methods : {
update : function() {
axios.post('/model/modifyUser/'+this.users.id, {
id: this.users.id,
firstName: this.users.firstName,
lastName: this.users.lastName,
email: this.users.email,
gender: this.users.gender,
ipAddress: this.users.ipAddress,
}).then(() => {
window.location.href='/model/'
}).catch((ex) => {
console.error("failed update user", ex)
})
}
}
}
created() 안에는 eventBus.$on로 이벤트를 받을 수 있다.
created()는 다음에 설명할 라이프 사이클 훅 중 하나인데,
쉽게말해 컴포넌트가 시작할 시점(created)에 이벤트 받는 것을 항상 on 시켜둔다(eventBus.$on)고 이해하면 될 것같다.
=> off 되기 이전까지 항상 이벤트를 받을 수 있다.
- eventBus on 첫번째 parameter는 받을 이벤트의 이름을 의미한다. 앞에서 설명한 event.$emit("A")를 하게 되면eventBus.$on("A" => (){})로 받게 된다.
- 두번째는 같이 보내져온 값을 사용하여 콜백 함수를 등록할 수 있다. 위의 예제에서는 보내져온 값을 사용하여 컴포넌트 data에 넣어준 것을 볼 수 있다.
앞서 말했듯 이벤트는 off 되기 이전까지 항상 이벤트를 받을 수 있기 때문에 컴포넌트가 종료(destoryed)된다거나 할때
이벤트를 종료(eventBus.$off("이벤트 이름")) 시켜주어야한다.
다만, 이벤트버스는 한정된 컴포넌트간의 통신에는 적합하지만 자주 사용하게 된 후,
규모가 커진다면 이벤트 추적 관리에 힘들어지는 단점이 있다.
(이를 관리하기 위해서 vuex 라이브러리를 이용할 수 있다.)