Component Communication in Vue 3: Props, Emit, Sibling Communication, Event Bus, Pinia, Provide/Inject, and Ref
This article provides a comprehensive guide to Vue 3 component communication, covering parent‑to‑child props, child‑to‑parent emit, sibling communication via parent mediation, event bus, Pinia state management, provide/inject, shared reactive objects, and component instance references, with code examples and a comparison table.
Introduction
In Vue 3, component communication is the foundation for building complex applications. The article explains three basic communication patterns—parent‑to‑child (props), child‑to‑parent (emit), and sibling communication—and expands to advanced techniques such as event bus, Pinia, provide/inject, shared reactive objects, and component instance references.
Parent‑to‑Child Communication (Props)
Parent components pass data to child components via props.
<template>
<div>
<child-component :message="parentMessage" :userInfo="userInfo"></child-component>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue';
import ChildComponent from './ChildComponent.vue';
const parentMessage = ref('Hello from Parent');
const userInfo = reactive({ name: 'John', age: 30 });
</script> <template>
<div>
<p>{{ message }}</p>
<p>{{ userInfo.name }}</p>
</div>
</template>
<script setup>
import { defineProps } from 'vue';
const props = defineProps({
message: String,
userInfo: Object,
});
</script>The parent binds parentMessage and userInfo to the child using v-bind (shorthand : ). The child receives them via defineProps .
Child‑to‑Parent Communication (Emit)
Children emit events that parents listen to using $emit and the @ (or v‑on ) directive.
<template>
<div>
<child-component @update="handleUpdate" @delete="handleDelete"></child-component>
</div>
</template>
<script setup>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';
const handleUpdate = (data) => {
console.log('Received from child:', data);
};
const handleDelete = () => {
// handle delete logic
};
</script> <template>
<div>
<button @click="handleClick">Update Parent</button>
</div>
</template>
<script setup>
import { defineEmits } from 'vue';
const emit = defineEmits(['update', 'delete']);
const handleClick = () => {
emit('update', { id: 1, value: 'new value' });
};
</script>The child defines emitable events with defineEmits and triggers them; the parent listens and processes the data.
Sibling Communication
Siblings usually communicate through a common parent or an event bus.
1. Parent Mediation
<template>
<div>
<sibling-a @message="handleMessage" />
<sibling-b :message="message" />
</div>
</template>
<script setup>
import { ref } from 'vue';
import SiblingA from './SiblingA.vue';
import SiblingB from './SiblingB.vue';
const message = ref('');
const handleMessage = (value) => {
message.value = value;
};
</script> <template>
<button @click="sendMessage">Send Message</button>
</template>
<script setup>
import { defineEmits } from 'vue';
const emit = defineEmits(['message']);
const sendMessage = () => {
emit('message', 'Hello from sibling A');
};
</script> <template>
<p>{{ message }}</p>
</template>
<script setup>
import { defineProps } from 'vue';
const props = defineProps(['message']);
</script>Sibling A emits a message event, the parent captures it, updates its state, and passes the value to sibling B via props.
2. Event Bus (mitt)
npm install mitt import { createApp } from 'vue';
import mitt from 'mitt';
import App from './App.vue';
const app = createApp(App);
app.config.globalProperties.$bus = mitt();
app.mount('#app'); <template>
<button @click="sendMessage">Send Message</button>
</template>
<script setup>
import { getCurrentInstance } from 'vue';
const { proxy } = getCurrentInstance();
const sendMessage = () => {
proxy.$bus.emit('myEvent', 'Hello from ComponentA');
};
</script> <script setup>
import { onMounted, getCurrentInstance } from 'vue';
const { proxy } = getCurrentInstance();
onMounted(() => {
proxy.$bus.on('myEvent', (message) => {
console.log(message); // "Hello from ComponentA"
});
});
</script>Component A emits an event on the global bus; Component B listens and reacts, achieving decoupled sibling communication.
3. Global State Management (Pinia)
// stores/message.js
import { defineStore } from 'pinia';
export const useMessageStore = defineStore('message', {
state: () => ({
content: ''
}),
actions: {
setContent(newContent) {
this.content = newContent;
}
}
}); <!-- ComponentA.vue -->
<template>
<button @click="update">Update Global State</button>
</template>
<script setup>
import { useMessageStore } from '@/stores/message';
const store = useMessageStore();
const update = () => {
store.setContent('Global message content');
};
</script> <!-- ComponentB.vue -->
<template>
<div>{{ store.content }}</div>
</template>
<script setup>
import { useMessageStore } from '@/stores/message';
const store = useMessageStore();
</script>Pinia provides a centralized, reactive store suitable for medium to large applications.
4. Provide/Inject + Reactive Data
<!-- Ancestor.vue -->
<template>
<ComponentA />
<ComponentB />
</template>
<script setup>
import { provide, ref } from 'vue';
const sharedData = ref('Initial data');
const updateData = (newValue) => {
sharedData.value = newValue;
};
provide('sharedContext', { sharedData, updateData });
</script> <!-- ComponentA.vue -->
<template>
<button @click="update">Modify Shared Data</button>
</template>
<script setup>
import { inject } from 'vue';
const { updateData } = inject('sharedContext');
const update = () => {
updateData('New data');
};
</script> <!-- ComponentB.vue -->
<template>
<div>{{ sharedData }}</div>
</template>
<script setup>
import { inject } from 'vue';
const { sharedData } = inject('sharedContext');
</script>Provides reactive data from an ancestor to any descendant without prop drilling.
5. Shared Reactive Object
// sharedState.js
import { reactive } from 'vue';
export const state = reactive({
value: '',
setValue(newVal) {
this.value = newVal;
}
}); <!-- ComponentA.vue -->
<template>
<input v-model="state.value" />
</template>
<script setup>
import { state } from './sharedState';
</script> <!-- ComponentB.vue -->
<template>
<div>{{ state.value }}</div>
</template>
<script setup>
import { state } from './sharedState';
</script>A simple reactive object can be imported by multiple components for quick state sharing.
6. Component Instance Reference (ref)
<!-- Parent.vue -->
<template>
<ComponentA ref="compA" />
<ComponentB :trigger="compA?.method" />
</template>
<script setup>
import { ref } from 'vue';
const compA = ref(null);
</script> <!-- ComponentA.vue -->
<script setup>
const method = () => {
console.log('ComponentA method called');
};
defineExpose({ method }); // expose method
</script> <!-- ComponentB.vue -->
<template>
<button @click="trigger">Call Method</button>
</template>
<script setup>
defineProps(['trigger']);
</script>Using ref and defineExpose allows a parent to call a child’s method directly, though it breaks encapsulation.
Method Comparison and Recommendation
Method
Advantages
Disadvantages
Recommended Scenarios
Props + Events
Official recommendation, clear data flow
Can become cumbersome with deep hierarchies
Simple parent‑child communication
Pinia
Professional state management, fully reactive
Learning curve
Medium to large applications
Event Bus
Fully decoupled, highly flexible
Manual event listener management
Temporary event communication
Provide/Inject
Efficient cross‑level communication
Data flow less explicit
Deeply nested components
Shared Object
Simple implementation
Hard to maintain
Small prototype projects
Component Instance Ref
Direct method access
Breaks component encapsulation
Special emergency scenarios
Conclusion
Vue 3 offers a rich set of component communication techniques—Props, Emit, sibling communication via parent mediation, event bus, Pinia, Provide/Inject, shared reactive objects, and component instance references. Each has distinct strengths and trade‑offs; selecting the appropriate method depends on the project’s size, complexity, and specific requirements.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.