서론 Vue 프로젝트에서 컴포넌트 간의 데이터 전달과 메서드 호출은 빈번하게 필요한 기능이다. Vuex 상태 관리를 사용하여 모든 데이터 전달 문제를 해결할 수도 있지만, 상황에 따라 적절한 방법을 선택하는 것이 중요하다. 이 글에서는 Vue에서 사용할 수 있는 다양한 컴포넌트 통신 방식을 상세히 다룬다.
컴포넌트 통신 방식의 분류
- 부모 → 자식 (props)
- 자식 → 부모 ($emit)
- 자식이 부모 메서드 호출 (props)
- 부모가 자식 데이터/메서드 참조 ($refs)
- 자식이 부모 데이터/메서드 참조 ($parent)
- 이벤트 버스를 통한 비父子 컴포넌트 통신
- Vuex를 통한 상태 관리
상세 구현 예제
- 부모 컴포넌트 → 자식 컴포넌트 데이터 전달
부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달하려면 props를 사용한다.
<template>
<div>
부모 컴포넌트:
<input type="text" v-model="userName">
<child-component :userName="userName"></child-component>
</div>
</template>
<script>
import ChildComponent from "./ChildComponent.vue";
export default {
components: {
ChildComponent
},
data() {
return {
userName: ""
};
}
};
</script>
자식 컴포넌트:
<template>
<div>
자식 컴포넌트:
<span>{{userName}}</span>
</div>
</template>
<script>
export default {
props: {
userName: {
type: String,
required: true
}
}
};
</script>
1-1. Props 값이 업데이트되지 않는 문제 해결
부모 컴포넌트에서 전달하는 값이 변경되어도 자식 컴포넌트에서 반영되지 않는 문제가 발생할 수 있다. 이 경우 중간 변수를 사용하고 watch로 감시하여 해결한다.
<template>
<input type="text" v-model="localValue" @input="handleInput" />
</template>
<script>
export default {
data() {
return {
localValue: this.inputValue,
additionalData: {}
};
},
props: {
inputValue: {
type: String
}
},
watch: {
inputValue(newValue) {
this.localValue = newValue;
},
additionalData: {
handler(val) {
// 처리 로직
},
deep: true
}
},
methods: {
handleInput() {
this.$emit("update:inputValue", this.localValue);
}
}
};
</script>
- 자식 컴포넌트 → 부모 컴포넌트 데이터 전달 ($emit)
자식 컴포넌트에서 부모 컴포넌트로 데이터를 전달하거나 부모의 메서드를 호출하려면 $emit을 사용한다.
부모 컴포넌트:
<template>
<div>
부모 컴포넌트:
<span>{{receivedData}}</span>
<child-component @sendData="handleChildData"></child-component>
</div>
</template>
<script>
import ChildComponent from "./ChildComponent.vue";
export default {
components: {
ChildComponent
},
data() {
return {
receivedData: ""
};
},
methods: {
handleChildData: function(data) {
this.receivedData = data;
}
}
};
</script>
자식 컴포넌트:
<template>
<div>
자식 컴포넌트:
<span>{{childData}}</span>
<input type="button" value="데이터 전송" @click="sendToParent">
</div>
</template>
<script>
export default {
data() {
return {
childData: "자식 컴포넌트의 데이터"
};
},
methods: {
sendToParent() {
this.$emit("sendData", this.childData);
}
}
};
</script>
- 자식 컴포넌트에서 부모 메서드 직접 호출
props를 통해 부모의 함수를 전달받아 직접 호출할 수 있다.
부모 컴포넌트:
<template>
<editor-component :onSave="saveContent"></editor-component>
</template>
<script>
export default {
methods: {
saveContent: function (content) {
alert("저장된 내용: " + content);
}
}
}
</script>
자식 컴포넌트:
<template>
<button @click="handleSave">저장</button>
</template>
<script>
export default {
props: {
onSave: {
type: Function,
default: null
}
},
methods: {
handleSave: function () {
if (this.onSave) {
this.onSave("부모에게 전달할 데이터");
}
}
}
}
</script>
- 부모가 자식 컴포넌트의 메서드 및 데이터 참조
$refs를 사용하면 부모 컴포넌트에서 자식 컴포넌트의 데이터와 메서드에 직접 접근할 수 있다.
<!-- 자식 컴포넌트에 ref属性 추가 -->
<child-component ref="childRef"></child-component>
<script>
export default {
methods: {
accessChild() {
// 자식 컴포넌트의 데이터 접근
console.log(this.$refs.childRef.childData);
// 자식 컴포넌트의 메서드 호출
this.$refs.childRef.childMethod();
}
}
};
</script>
- 자식이 부모 컴포넌트의 메서드 및 데이터 참조
$parent를 사용하여 부모 컴포넌트에 접근할 수 있다.多层嵌套 구조에서는 this.$parent.$parent 형태로 연속 접근이 가능하다.
<script>
export default {
methods: {
accessParent() {
console.log(this.$parent.parentData);
this.$parent.parentMethod();
}
}
};
</script>
참고: 이 방법은 부모 컴포넌트의 구조를 명확히 알고 있어야 하므로, 컴포넌트 중첩이 깊어질 경우 사용을 권장하지 않는다.
- 비父子 컴포넌트 간 통신
父子 관계가 아닌 컴포넌트 간 통신은 다음과 같은 방법으로 구현할 수 있다:
방법 1: 공통 부모 컴포넌트 활용 두 컴포넌트가 동일한 부모를 공유하는 경우, 부모 컴포넌트의 data를 통해 데이터를 중개할 수 있다.
방법 2: Event Bus (이벤트 버스) Vue 2.0에서 사용할 수 있는 Publish-Subscribe 패턴이다. 이벤트 버스를 전역으로 설정하여 사용한다.
// main.js
window.eventBus = new Vue();
컴포넌트 A (데이터 수신):
<template>
<div>
A 컴포넌트:
<input type="button" value="증가" @click="increment">
<span>{{counter}}</span>
</div>
</template>
<script>
export default {
data() {
return {
counter: 0
};
},
methods: {
increment() {
this.counter++;
},
displayMessage() {
console.log("A 컴포넌트 메서드 호출됨");
}
},
mounted() {
const vm = this;
eventBus.$on("updateCounter", function(data) {
console.log("수신된 데이터:", data);
vm.counter = data;
vm.displayMessage();
});
}
};
</script>
컴포넌트 B (데이터 전송):
<template>
<div>
B 컴포넌트:
<span>{{currentValue}}</span>
<input type="button" value="전송" @click="sendToComponentA">
</div>
</template>
<script>
export default {
data() {
return {
currentValue: 10
};
},
methods: {
sendToComponentA() {
eventBus.$emit("updateCounter", this.currentValue);
}
}
};
</script>
- Vuex를 통한 상태 관리
복잡한 애플리케이션에서는 Vuex를 사용하여 중앙 집중식 상태 관리를 수행하는 것이 효과적이다.
<template>
<div>
<button @click="updateValue">값 변경</button>
<p>Store 값: {{this.$store.state.sharedData}}</p>
<p>Getter 값: {{computedData}}</p>
</div>
</template>
<script>
export default {
data() {
return {};
},
computed: {
computedData() {
return this.$store.getters.getSharedData;
}
},
watch: {
computedData(newValue, oldValue) {
console.log("변경된 값:", newValue);
}
},
methods: {
updateValue() {
this.$store.commit("updateSharedData", this.$store.state.sharedData + 1);
}
}
};
</script>
총정리
Vue 컴포넌트 간 통신 방식은 다음과 같이 정리할 수 있다:
- props: 부모 → 자식 데이터 전달
- $emit: 자식 → 부모 데이터 전달 및 이벤트 발생
- props (함수): 자식이 부모 메서드 호출
- $refs: 부모가 자식 컴포넌트 직접 참조
- $parent: 자식이 부모 컴포넌트 직접 참조
- Event Bus: 비父子 컴포넌트 간 통신
- Vuex: 전역 상태 관리
각 방식은 고유한 사용 시나리오가 있으므로, 프로젝트의 규모와 구조에 맞게 적절한 방법을 선택해야 한다.