下面是 Vue2 和 Vue3 中组件通信方式的详细对比和说明:
一、共同支持的通信方式
1. Props / $emit (父子组件通信)
Vue2 和 Vue3 用法相同
// 父组件
<Child :message="msg" @update="handleUpdate" />
// 子组件
this.$emit('update', newValue)
2. 事件总线 (Event Bus)
Vue2 和 Vue3 用法相似
// Vue2
const bus = new Vue()
bus.$emit('event')
bus.$on('event', callback)
// Vue3 (需要引入第三方事件库或自定义)
import { mitt } from 'mitt'
const emitter = mitt()
emitter.emit('event')
emitter.on('event', callback)
3. Vuex 状态管理
Vue2 和 Vue3 用法基本相同
// 组件中使用
this.$store.commit('mutation')
this.$store.dispatch('action')
4. provide / inject
Vue2 和 Vue3 都支持,但 Vue3 的 Composition API 更强大
// Vue2
export default {
provide() {
return { theme: this.theme }
},
inject: ['theme']
}
// Vue3
import { provide, inject } from 'vue'
setup() {
provide('theme', 'dark')
const theme = inject('theme')
}
5. $refs
Vue2 和 Vue3 用法相同
<Child ref="child" />
this.$refs.child.method()
二、Vue2 特有通信方式
1.
children
this.$parent.method()
this.$children[0].data
注意:Vue3 已移除 $children API
2. $listeners / .sync 修饰符
// Vue2 .sync
<Child :title.sync="pageTitle" />
// Vue2 $listeners
<Child v-on="$listeners" />
注意:Vue3 中已移除 $listeners,.sync 被 v-model 参数替代
三、Vue3 新增/改进的通信方式
1. v-model 增强
// Vue3 支持多个 v-model
<Child v-model:first="first" v-model:second="second" />
// 子组件
emit('update:first', value)
emit('update:second', value)
2. Composition API 的 provide/inject
import { provide, inject } from 'vue'
// 提供者
provide('key', reactiveValue)
// 消费者
const value = inject('key', defaultValue)
3. 自定义 Hook 复用逻辑
// useCounter.js
export function useCounter() {
const count = ref(0)
function increment() {
count.value++
}
return { count, increment }
}
// 组件中使用
const { count, increment } = useCounter()
4. Teleport 组件
<teleport to="body">
<Modal /> <!-- 可以将组件渲染到DOM任意位置 -->
</teleport>
5. 响应式 API 共享状态
// store.js
import { reactive } from 'vue'
export const store = reactive({ count: 0 })
// 组件中使用
import { store } from './store'
store.count++
四、推荐选择
Vue2 项目:
- 简单通信:props/$emit
- 跨组件:事件总线/Vuex
- 深层嵌套:provide/inject
Vue3 项目:
- 简单通信:props/emit + v-model
- 跨组件:Vuex/Pinia + Composition API
- 状态共享:响应式API + provide/inject
- 逻辑复用:自定义Hook
五、注意事项
- Vue3 中移除了
off, $once 方法,事件总线需要第三方库
- Vue3 推荐使用 Pinia 替代 Vuex 作为状态管理工具
- Composition API 提供了更灵活的状态共享方式
- 避免过度使用
children,会使组件耦合度过高
根据项目需求和组件关系选择合适的通信方式,Vue3 的组合式API为组件通信提供了更多灵活的选择。
Vue2 和 Vue3 组件通信方式超详细解析
下面我将从8个维度全面对比Vue2和Vue3的组件通信方式,包含原理、使用场景、最佳实践和完整代码示例。
一、Props / $emit 通信(父子组件)
Vue2 实现
// 父组件 Parent.vue
<template>
<Child
:title="pageTitle"
:content="pageContent"
@title-change="handleTitleChange"
/>
</template>
<script>
export default {
data() {
return {
pageTitle: '初始标题',
pageContent: '初始内容'
}
},
methods: {
handleTitleChange(newTitle) {
this.pageTitle = newTitle
console.log('标题更新为:', newTitle)
}
}
}
</script>
// 子组件 Child.vue
<template>
<div>
<h1>{{ title }}</h1>
<p>{{ content }}</p>
<button @click="updateTitle">更新标题</button>
</div>
</template>
<script>
export default {
props: {
title: String,
content: String
},
methods: {
updateTitle() {
this.$emit('title-change', '新标题-' + Date.now())
}
}
}
</script>
Vue3 实现
// 父组件 Parent.vue
<template>
<Child
:title="pageTitle"
:content="pageContent"
@title-change="handleTitleChange"
/>
</template>
<script setup>
import { ref } from 'vue'
const pageTitle = ref('初始标题')
const pageContent = ref('初始内容')
const handleTitleChange = (newTitle) => {
pageTitle.value = newTitle
console.log('标题更新为:', newTitle)
}
</script>
// 子组件 Child.vue
<template>
<div>
<h1>{{ title }}</h1>
<p>{{ content }}</p>
<button @click="updateTitle">更新标题</button>
</div>
</template>
<script setup>
const props = defineProps({
title: String,
content: String
})
const emit = defineEmits(['title-change'])
const updateTitle = () => {
emit('title-change', '新标题-' + Date.now())
}
</script>
对比分析
特性 | Vue2 | Vue3 |
---|---|---|
定义方式 | options API | script setup + 组合式API |
props声明 | props选项 | defineProps宏 |
事件定义 | 模板中直接使用$emit | defineEmits宏 |
TypeScript | 需要额外类型声明 | 完美支持类型推断 |
二、事件总线通信(跨组件)
Vue2 实现
// eventBus.js
import Vue from 'vue'
export const EventBus = new Vue()
// 组件A(发布事件)
import { EventBus } from './eventBus'
EventBus.$emit('global-event', { data: 123 })
// 组件B(订阅事件)
import { EventBus } from './eventBus'
EventBus.$on('global-event', payload => {
console.log('收到事件:', payload)
})
// 组件卸载时需要手动移除监听
beforeDestroy() {
EventBus.$off('global-event')
}
Vue3 实现(推荐使用mitt)
// eventBus.js
import mitt from 'mitt'
export const emitter = mitt()
// 组件A(发布事件)
import { emitter } from './eventBus'
emitter.emit('global-event', { data: 123 })
// 组件B(订阅事件)
import { emitter } from './eventBus'
import { onMounted, onUnmounted } from 'vue'
onMounted(() => {
emitter.on('global-event', payload => {
console.log('收到事件:', payload)
})
})
onUnmounted(() => {
emitter.off('global-event')
})
深度解析
- Vue3移除$on原因:减少体积,解耦事件系统
-
mitt优势:
- 更小(200字节)
- 支持通配符'*'监听所有事件
- 清晰的TypeScript支持
-
性能考虑:
- 大型应用慎用,可能造成事件混乱
- 适合小范围组件通信
三、Vuex/Pinia 状态管理
Vue2 + Vuex
// store/index.js
export default new Vuex.Store({
state: {
count: 0,
user: null
},
mutations: {
increment(state) {
state.count++
},
setUser(state, user) {
state.user = user
}
},
actions: {
async fetchUser({ commit }) {
const user = await api.getUser()
commit('setUser', user)
}
}
})
// 组件中使用
this.$store.commit('increment')
this.$store.dispatch('fetchUser')
const count = this.$store.state.count
Vue3 + Pinia(推荐)
// stores/counter.js
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
user: null
}),
actions: {
increment() {
this.count++
},
async fetchUser() {
this.user = await api.getUser()
}
}
})
// 组件中使用
import { useCounterStore } from '@/stores/counter'
const counter = useCounterStore()
counter.increment()
counter.fetchUser()
const count = counter.count
核心差异对比
特性 | Vuex | Pinia |
---|---|---|
API风格 | 命令式 | 组合式 |
模块系统 | 需要modules | 自动模块化 |
TypeScript | 需要类型定义 | 开箱即用 |
体积 | 较大 | 更轻量 |
调试工具 | Vue Devtools | Vue Devtools |
异步处理 | 需要actions | 可直接修改状态 |
四、provide/inject(依赖注入)
Vue2 实现
// 祖先组件
export default {
provide() {
return {
themeData: {
color: 'dark',
toggleTheme: this.toggleTheme
}
}
},
methods: {
toggleTheme() {
// 切换主题逻辑
}
}
}
// 后代组件
export default {
inject: ['themeData'],
created() {
console.log(this.themeData.color)
}
}
Vue3 实现(组合式API)
// 祖先组件
import { provide, reactive } from 'vue'
setup() {
const theme = reactive({
color: 'dark',
toggleTheme: () => {
theme.color = theme.color === 'dark' ? 'light' : 'dark'
}
})
provide('theme', theme)
}
// 后代组件
import { inject } from 'vue'
setup() {
const theme = inject('theme')
console.log(theme.color)
return { theme }
}
高级用法
- 响应式注入:
// 提供响应式对象
const state = reactive({ count: 0 })
provide('sharedState', readonly(state)) // 使用readonly防止意外修改
- 注入默认值:
const config = inject('app-config', { theme: 'default' })
- Symbol作为key(避免命名冲突):
// keys.js
export const THEME_KEY = Symbol('theme')
// 提供者
provide(THEME_KEY, theme)
// 消费者
const theme = inject(THEME_KEY)
五、模板引用通信($refs)
Vue2 实现
// 父组件
<template>
<ChildComponent ref="child" />
<button @click="callChildMethod">调用子方法</button>
</template>
<script>
export default {
methods: {
callChildMethod() {
this.$refs.child.doSomething()
console.log(this.$refs.child.someData)
}
}
}
</script>
// 子组件
export default {
data() {
return {
someData: '子组件数据'
}
},
methods: {
doSomething() {
console.log('子组件方法被调用')
}
}
}
Vue3 实现
// 父组件
<template>
<ChildComponent ref="childRef" />
<button @click="callChildMethod">调用子方法</button>
</template>
<script setup>
import { ref } from 'vue'
const childRef = ref(null)
const callChildMethod = () => {
childRef.value.doSomething()
console.log(childRef.value.someData)
}
</script>
// 子组件
<script setup>
import { ref } from 'vue'
const someData = ref('子组件数据')
const doSomething = () => {
console.log('子组件方法被调用')
}
// 需要暴露的属性和方法
defineExpose({
someData,
doSomething
})
</script>
关键区别
- Vue3必须使用defineExpose:明确暴露哪些内容可被访问
- TypeScript支持:Vue3能获得完整的类型提示
- 组合式API:使用ref引用替代this.$refs
六、v-model 双向绑定
Vue2 实现
// 父组件
<ChildComponent v-model="pageTitle" />
// 等价于
<ChildComponent
:value="pageTitle"
@input="pageTitle = $event"
/>
// 子组件
export default {
props: ['value'],
methods: {
updateValue(newVal) {
this.$emit('input', newVal)
}
}
}
Vue3 实现
// 父组件
<ChildComponent v-model="pageTitle" />
// 等价于
<ChildComponent
:modelValue="pageTitle"
@update:modelValue="newValue => pageTitle = newValue"
/>
// 子组件
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
const updateValue = (e) => {
emit('update:modelValue', e.target.value)
}
</script>
Vue3 高级用法
- 多个v-model绑定:
<UserName
v-model:first-name="first"
v-model:last-name="last"
/>
// 子组件
defineProps({
firstName: String,
lastName: String
})
defineEmits(['update:firstName', 'update:lastName'])
- 自定义修饰符:
<MyComponent v-model.capitalize="myText" />
// 子组件中可以处理修饰符
const props = defineProps({
modelValue: String,
modelModifiers: { default: () => ({}) }
})
const emitValue = (e) => {
let value = e.target.value
if (props.modelModifiers.capitalize) {
value = value.charAt(0).toUpperCase() + value.slice(1)
}
emit('update:modelValue', value)
}
七、本地存储通信
通用实现方案
// storage.js
export const storage = {
get(key) {
const value = localStorage.getItem(key)
return value ? JSON.parse(value) : null
},
set(key, value) {
localStorage.setItem(key, JSON.stringify(value))
},
remove(key) {
localStorage.removeItem(key)
},
clear() {
localStorage.clear()
}
}
// 组件A(设置数据)
import { storage } from './storage'
storage.set('shared-data', { key: 'value' })
// 组件B(获取数据)
const data = storage.get('shared-data')
响应式封装(Vue3)
// useStorage.js
import { ref, watchEffect } from 'vue'
export function useStorage(key, defaultValue) {
const data = ref(JSON.parse(localStorage.getItem(key)) || defaultValue)
watchEffect(() => {
localStorage.setItem(key, JSON.stringify(data.value))
})
return data
}
// 组件中使用
const sharedData = useStorage('shared-key', { init: 'value' })
sharedData.value.newProp = 'update' // 会自动同步到localStorage
注意事项
- 存储限制:localStorage通常有5MB限制
- 性能影响:频繁操作可能影响性能
- 安全考虑:不要存储敏感信息
- 类型安全:JSON序列化会丢失函数和原型链
八、Web Workers 跨线程通信
Vue 集成方案
// worker.js
self.onmessage = function(e) {
console.log('Worker收到:', e.data)
const result = heavyCalculation(e.data)
self.postMessage(result)
}
function heavyCalculation(data) {
// 耗时计算...
return data * 2
}
// 组件中使用
const worker = new Worker('./worker.js')
// 发送消息
worker.postMessage(42)
// 接收消息
worker.onmessage = (e) => {
console.log('主线程收到:', e.data)
}
// 错误处理
worker.onerror = (error) => {
console.error('Worker错误:', error)
}
Vue3 封装示例
// useWorker.js
import { ref } from 'vue'
export function useWorker(workerPath) {
const result = ref(null)
const error = ref(null)
const loading = ref(false)
const worker = new Worker(workerPath)
const postMessage = (data) => {
loading.value = true
worker.postMessage(data)
}
worker.onmessage = (e) => {
result.value = e.data
loading.value = false
}
worker.onerror = (e) => {
error.value = e
loading.value = false
}
return {
result,
error,
loading,
postMessage
}
}
// 组件中使用
const { result, postMessage } = useWorker('./worker.js')
postMessage(42)
最佳实践总结
-
父子通信:
- 简单数据流:props/emit
- 双向绑定:v-model(Vue3支持多v-model)
-
兄弟组件:
- 小应用:事件总线
- 中大型应用:Pinia/Vuex
-
深层嵌套:
- provide/inject(Vue3组合式API更强大)
-
复杂状态:
- Vue3首选Pinia
- Vue2大型项目用Vuex
-
跨组件方法调用:
- $refs(Vue3需要defineExpose)
- 优先考虑状态管理而非直接方法调用
-
持久化数据:
- 本地存储方案
- 考虑加密敏感数据
-
性能敏感操作:
- Web Workers后台计算
- 避免在main线程执行耗时操作
-
TypeScript项目:
- Vue3 + Pinia + Composition API获得最佳类型支持
- 为Vue2的Vuex添加类型定义
根据项目规模、团队习惯和技术栈选择合适的通信方式,保持一致性比追求新技术更重要。