Vue.js Component Communication Methods and Practical Examples
Vue.js offers multiple component communication techniques—including one‑way props, $emit/$on events, an event bus, $attrs/$listeners, provide/inject, direct $parent/$children/$refs access, Vuex state management, and slot‑based patterns—enabling flexible data flow between parents, children, siblings, and distant descendants.
Vue.js components have isolated scopes, which means data cannot be directly referenced between different components. Understanding component communication is essential for building complex Vue applications.
1. props and $emit
Child components receive data from parent components through props, which are one‑way (parent → child). Parents can listen to events emitted by children using $emit.
// Parent.vue
<template>
<div id="parent">
<Child :msg="msg" />
</div>
</template>
<script>
import Child from './Child.vue'
export default {
name: 'parent',
data() { return { msg: 'Data from parent' } },
components: { Child }
}
</script>
// Child.vue
<template>
<div id="child">
<div>{{ msg }}</div>
</div>
</template>
<script>
export default {
name: 'child',
props: { msg: String }
}
</script>Children can send data back to parents by emitting custom events:
// Parent.vue (continued)
<template>
<div id="parent">
<div>{{ msg }}</div>
<Child2 @changeMsg="parentMsg" />
</div>
</template>
<script>
import Child2 from './Child2.vue'
export default {
name: 'parent',
data() { return { msg: '' } },
methods: { parentMsg(msg) { this.msg = msg } },
components: { Child2 }
}
</script>
// Child2.vue
<template>
<div id="child">
<button @click="childMsg">Pass data to parent</button>
</div>
</template>
<script>
export default {
name: 'child',
methods: {
childMsg() { this.$emit('changeMsg', 'Data from child') }
}
}
</script>2. $emit and $on (Event Bus)
By creating a shared Vue instance, components can emit and listen to events across any relationship (parent‑child, sibling, or grand‑parent).
// Event bus instance
const Event = new Vue();
// Parent.vue
<template>
<div>
<Child1 :Event="Event" />
<Child2 :Event="Event" />
<Child3 :Event="Event" />
</div>
</template>
<script>
import Vue from 'vue';
import Child1 from './Child1.vue';
import Child2 from './Child2.vue';
import Child3 from './Child3.vue';
export default {
name: 'parent',
data() { return { Event } },
components: { Child1, Child2, Child3 }
}
</script>
// Child1.vue
<template>
<div>
1、Name: {{ name }}
<button @click="send">Send to Child3</button>
</div>
</template>
<script>
export default {
name: 'child1',
data() { return { name: 'Corgi Hui' } },
props: { Event: Object },
methods: {
send() { this.Event.$emit('msgA', this.name) }
}
}
</script>
// Child3.vue (listening)
<script>
export default {
name: 'child3',
data() { return { name: '', height: '' } },
props: { Event: Object },
mounted() {
this.Event.$on('msgA', name => { this.name = name })
this.Event.$on('msgB', height => { this.height = height })
}
}
</script>3. $attrs and $listeners $attrs contains all parent‑bound attributes that are not declared as props (except class and style). $listeners contains all parent‑bound event listeners (except those with the .native modifier). They are useful for building higher‑order components.
// Parent.vue
<template>
<div id="parentAttrs">
<MyInput placeholder="Enter your name" type="text" title="Name" v-model="name" />
</div>
</template>
<script>
import MyInput from './MyInput.vue'
export default { name: 'parent', data() { return { name: '' } }, components: { MyInput } }
</script>
// MyInput.vue
<template>
<div>
<label>Name:</label>
<input v-bind="$attrsAll" @input="$emit('input', $event.target.value)" />
</div>
</template>
<script>
export default {
name: 'myinput',
inheritAttrs: false,
computed: {
$attrsAll() { return { value: this.$vnode.data.model.value, ...this.$attrs } }
}
}
</script>
// Parent listening to events via $listeners
<template>
<div id="parentListener">
<MyInput @focus="focus" placeholder="Enter your name" type="text" title="Name" v-model="name" />
</div>
</template>
<script>
import MyInput from './MyInput.vue'
export default {
name: 'parent',
data() { return { name: '' } },
methods: { focus() { console.log('focus triggered') } },
components: { MyInput }
}
</script>
// MyInput handling $listeners
<template>
<div>
<label>Name:</label>
<input v-bind="$attrsAll" v-on="$listenersAll" />
<button @click="handlerF">Test</button>
</div>
</template>
<script>
export default {
name: 'myinput',
inheritAttrs: false,
props: ['value'],
methods: { handlerF() { this.$emit('focus') } },
computed: {
$attrsAll() { return { value: this.value, ...this.$attrs } },
$listenersAll() { return Object.assign({}, this.$listeners, { input: event => this.$emit('input', event.target.value) }) }
}
}
</script>4. provide and inject
These APIs allow an ancestor component to provide data that any descendant can inject, regardless of the component depth.
// Parent.vue (provides)
<template>
<div class="parentProvide">
<button @click="changeSth">Change Something</button>
<p>Current: {{ sth }}</p>
<ChildA />
</div>
</template>
<script>
import ChildA from './ChildA.vue'
export default {
name: 'parent-pro',
data() { return { sth: 'Eat' } },
provide() { return { obj: this } },
methods: { changeSth() { this.sth = 'Sleep' } },
components: { ChildA }
}
</script>
// ChildA.vue (injects)
<template>
<div>
<p>ChildA sees: {{ obj.sth }}</p>
<ChildB />
</div>
</template>
<script>
import ChildB from './ChildB.vue'
export default {
name: 'child-a',
inject: { obj: { default: () => ({}) } },
components: { ChildB }
}
</script>
// ChildB.vue (injects the same object)
<template>
<div>
<p>ChildB sees: {{ obj.sth }}</p>
</div>
</template>
<script>
export default {
name: 'child-b',
inject: { obj: { default: () => ({}) } }
}
</script>5. $parent, $children & $refs
These properties give direct access to a component’s immediate parent, its children array, and referenced elements/components.
// Parent.vue
<template>
<div class="parentPC">
<p>Name: {{ name }}</p>
<p>Title: {{ title }}</p>
<ChildA ref="comp1" />
<ChildB ref="comp2" />
</div>
</template>
<script>
import ChildA from './ChildA.vue'
import ChildB from './ChildB.vue'
export default {
name: 'parent-pc',
data() { return { name: '', title: '', contentToA: 'parent-pc-to-A', contentToB: 'parent-pc-to-B' } },
mounted() {
const comp1 = this.$refs.comp1
this.title = comp1.title
comp1.sayHi()
this.name = this.$children[1].title
},
components: { ChildA, ChildB }
}
</script>
// ChildA.vue (uses ref)
<template>
<div>
<p>(ChildA) Parent is: {{ content }}</p>
</div>
</template>
<script>
export default {
name: 'child-a',
data() { return { title: 'I am ChildA', content: '' } },
methods: { sayHi() { console.log('Hi, girl~') } },
mounted() { this.content = this.$parent.contentToA }
}
</script>
// ChildB.vue (uses $children)
<template>
<div>
<p>(ChildB) Parent is: {{ content }}</p>
</div>
</template>
<script>
export default {
name: 'child-b',
data() { return { title: 'I am ChildB', content: '' } },
mounted() { this.content = this.$parent.contentToB }
}
</script>6. Vuex (State Management)
Vuex provides a centralized store for all component states, enforcing a unidirectional data flow.
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = { userInfo: {} }
const getters = {
getUserInfo(states) { return states.userInfo }
}
const mutations = {
UPDATE_USERINFO(states, obj) { states.userInfo = obj }
}
const actions = {
update_userinfo({ commit }, param) { commit('UPDATE_USERINFO', param) },
incrementAsync({ commit }) { setTimeout(() => commit('increment'), 1000) }
}
export default new Vuex.Store({ state, getters, mutations, actions })7. slot‑scope and v‑slot
From Vue 2.6, named and scoped slots use the v-slot directive.
// BaseLayout.vue (slot placeholders)
<template>
<div class="container">
<header><slot name="header"></slot></header>
<main><slot></slot></main>
<footer><slot name="footer"></slot></footer>
</div>
</template>
<script>
export default { name: 'base-layout' }
</script>
// Parent.vue (providing slot content)
<template>
<BaseLayout>
<template v-slot:header><h1>Here might be a page title</h1></template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template v-slot:footer><p>Here's some contact info</p></template>
</BaseLayout>
</template>
<script>
import BaseLayout from './BaseLayout.vue'
export default { name: 'parent-slot', components: { BaseLayout } }
</script>8. scopedSlots property (render function)
When using render functions, scoped slots can be passed via the scopedSlots option.
// baseLayout.vue (rendering scoped slots)
<script>
export default {
data() { return { headerText: 'child header text', defaultText: 'child default text', footerText: 'child footer text' } },
render(h) {
return h('div', { class: 'child-node' }, [
this.$scopedSlots.header({ text: this.headerText }),
this.$scopedSlots.default(this.defaultText),
this.$scopedSlots.footer({ text: this.footerText })
])
}
}
</script>
// Parent component using scopedSlots
<script>
import BaseLayout from './baseLayout'
export default {
name: 'ScopedSlots',
components: { BaseLayout },
render(h) {
return h('div', { class: 'parent-node' }, [
this.$slots.default,
h('base-layout', {
scopedSlots: {
header: props => h('p', { style: { color: 'red' } }, props.text),
default: props => h('p', { style: { color: 'deeppink' } }, props),
footer: props => h('p', { style: { color: 'orange' } }, props.text)
}
})
])
}
}
</script>In summary, Vue component communication can be categorized into three scenarios:
Parent‑child: props, $emit/$on, Vuex, $attrs/$listeners, provide/inject, $parent/$children/$refs Sibling: $emit/$on, Vuex
Cross‑level (grandparent‑grandchild): $emit/$on, Vuex, provide/inject, $attrs/$listeners These techniques enable flexible data flow and method sharing across Vue component hierarchies.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
