Frontend Development 13 min read

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.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Component Communication in Vue 3: Props, Emit, Sibling Communication, Event Bus, Pinia, Provide/Inject, and Ref

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.

frontendPiniaVue3component communicationEmitPropsevent bus
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.