大家好,我是小杨,一个摸爬滚打了6年的前端老油条。今天咱们来聊聊Vue组件通信这个经典话题------在不使用Vuex的情况下,如何优雅地实现父子组件、兄弟组件之间的数据传递?相信我,掌握这些方法后,你会发现Vuex并不是唯一的选择。
一、父子组件通信的4种常规武器
1. Props Down(父传子)
这是最基础的通信方式,父组件通过props向子组件传递数据:
javascript
// 父组件
<template>
<child-component :message="parentMessage" />
</template>
<script>
export default {
data() {
return {
parentMessage: '我是父组件传来的消息'
}
}
}
</script>
// 子组件
<script>
export default {
props: {
message: {
type: String,
default: ''
}
},
mounted() {
console.log(this.message) // 输出:我是父组件传来的消息
}
}
</script>
适用场景:简单的单向数据流,父组件控制子组件显示内容
2. Events Up(子传父)
子组件通过$emit触发事件,父组件监听这些事件:
javascript
// 子组件
<template>
<button @click="sendMessage">点我传消息</button>
</template>
<script>
export default {
methods: {
sendMessage() {
this.$emit('message-from-child', '我是子组件的问候')
}
}
}
</script>
// 父组件
<template>
<child-component @message-from-child="handleMessage" />
</template>
<script>
export default {
methods: {
handleMessage(msg) {
console.log(msg) // 输出:我是子组件的问候
}
}
}
</script>
适用场景:子组件需要通知父组件某些操作或状态变化
3. ref直接访问
父组件通过ref直接调用子组件的方法或访问数据:
javascript
// 子组件
<script>
export default {
data() {
return {
childData: '我是子组件的数据'
}
},
methods: {
showMessage() {
console.log('子组件方法被调用')
}
}
}
</script>
// 父组件
<template>
<child-component ref="myChild" />
</template>
<script>
export default {
mounted() {
console.log(this.$refs.myChild.childData) // 输出:我是子组件的数据
this.$refs.myChild.showMessage() // 输出:子组件方法被调用
}
}
</script>
适用场景:需要直接操作子组件时(慎用,容易造成紧耦合)
4. v-model双向绑定
实现父子组件的双向数据绑定:
javascript
// 子组件
<template>
<input :value="value" @input="$emit('input', $event.target.value)" />
</template>
<script>
export default {
props: ['value']
}
</script>
// 父组件
<template>
<child-component v-model="parentValue" />
</template>
<script>
export default {
data() {
return {
parentValue: ''
}
}
}
</script>
适用场景:表单控件等需要双向绑定的场景
二、兄弟组件通信的3种骚操作
1. 通过共同的父组件中转
这是最正统的兄弟组件通信方式:
javascript
// 父组件
<template>
<child-a @change="handleChange" />
<child-b :message="sharedMessage" />
</template>
<script>
export default {
data() {
return {
sharedMessage: ''
}
},
methods: {
handleChange(msg) {
this.sharedMessage = msg
}
}
}
</script>
// ChildA组件
<script>
export default {
methods: {
sendMessage() {
this.$emit('change', '来自A组件的消息')
}
}
}
</script>
// ChildB组件
<script>
export default {
props: ['message'],
watch: {
message(newVal) {
console.log(newVal) // 输出:来自A组件的消息
}
}
}
</script>
适用场景:兄弟组件有共同父组件且层级不深的情况
2. Event Bus(事件总线)
创建一个中央事件总线来实现任意组件间通信:
javascript
// eventBus.js
import Vue from 'vue'
export const EventBus = new Vue()
// 组件A(发送事件)
<script>
import { EventBus } from './eventBus'
export default {
methods: {
sendMessage() {
EventBus.$emit('my-event', '我是组件A的消息')
}
}
}
</script>
// 组件B(接收事件)
<script>
import { EventBus } from './eventBus'
export default {
created() {
EventBus.$on('my-event', msg => {
console.log(msg) // 输出:我是组件A的消息
})
},
beforeDestroy() {
// 记得移除监听,避免内存泄漏
EventBus.$off('my-event')
}
}
</script>
适用场景:任意组件间通信,特别是没有直接关系的组件
坑点提示:一定要记得在组件销毁时移除事件监听,否则会导致内存泄漏!
3. 全局变量挂载
在Vue原型上挂载共享对象:
javascript
// main.js
Vue.prototype.$shared = {
data: '',
updateData(newData) {
this.data = newData
}
}
// 组件A
<script>
export default {
methods: {
updateSharedData() {
this.$shared.updateData('新的共享数据')
}
}
}
</script>
// 组件B
<script>
export default {
created() {
console.log(this.$shared.data) // 输出:新的共享数据
}
}
</script>
适用场景:简单的全局状态共享
注意事项:这种方法不够响应式,需要手动触发更新
三、进阶通信方案(适合复杂场景)
1. Provide/Inject
实现祖先组件向后代组件传值(跨多级传递):
javascript
// 祖先组件
<script>
export default {
provide() {
return {
ancestorData: '我是祖先的数据'
}
}
}
</script>
// 后代组件(任意层级)
<script>
export default {
inject: ['ancestorData'],
created() {
console.log(this.ancestorData) // 输出:我是祖先的数据
}
}
</script>
适用场景:深层嵌套组件通信
限制:provide/inject绑定不是响应式的(除非传递一个响应式对象)
2. 使用浏览器存储
通过localStorage或sessionStorage实现组件间通信:
javascript
// 组件A
<script>
export default {
methods: {
saveData() {
localStorage.setItem('sharedKey', JSON.stringify({ msg: '跨组件消息' }))
}
}
}
</script>
// 组件B
<script>
export default {
created() {
window.addEventListener('storage', this.handleStorageChange)
},
methods: {
handleStorageChange(e) {
if (e.key === 'sharedKey') {
const data = JSON.parse(e.newValue)
console.log(data.msg) // 输出:跨组件消息
}
}
},
beforeDestroy() {
window.removeEventListener('storage', this.handleStorageChange)
}
}
</script>
适用场景:跨标签页通信或数据持久化
3. 使用Observable实现简易状态管理
Vue.observable可以创建响应式对象:
javascript
// sharedState.js
import Vue from 'vue'
export const state = Vue.observable({
count: 0
})
export const mutations = {
increment() {
state.count++
}
}
// 组件A
<script>
import { state, mutations } from './sharedState'
export default {
methods: {
incrementCount() {
mutations.increment()
}
}
}
</script>
// 组件B
<script>
import { state } from './sharedState'
export default {
computed: {
count() {
return state.count
}
}
}
</script>
适用场景:中小型应用的状态管理
四、如何选择合适的通信方式?
经过多年踩坑,我总结出以下选择原则:
-
父子组件:
- 简单数据传递:props down / events up
- 需要双向绑定:v-model
- 需要直接调用方法:ref(慎用)
-
兄弟组件:
- 有共同父组件:通过父组件中转
- 无直接关系:Event Bus或全局状态
-
跨多级组件:
- provide/inject
- 全局状态管理
-
需要持久化:
- 浏览器存储方案
五、实战建议
- 避免过度设计:简单场景就用props和events,别上来就搞复杂方案
- 注意内存泄漏:使用Event Bus一定要记得$off
- 保持单向数据流:尽量让数据流向可预测
- 考虑可维护性:选择团队熟悉的方案,而不是最"高级"的方案
记住,没有最好的方案,只有最适合的方案。根据你的具体场景选择最合适的通信方式,而不是盲目追求技术的新颖性。
⭐ 写在最后
请大家不吝赐教,在下方评论或者私信我,十分感谢🙏🙏🙏.
✅ 认为我某个部分的设计过于繁琐,有更加简单或者更高逼格的封装方式
✅ 认为我部分代码过于老旧,可以提供新的API或最新语法
✅ 对于文章中部分内容不理解
✅ 解答我文章中一些疑问
✅ 认为某些交互,功能需要优化,发现BUG
✅ 想要添加新功能,对于整体的设计,外观有更好的建议
✅ 一起探讨技术加qq交流群:906392632
最后感谢各位的耐心观看,既然都到这了,点个 👍赞再走吧!