Vue2与Vue3通信机制对比

Vue 3 在通信机制上进行了重大改进,移除了部分 Vue 2 API,引入了 Composition API 的函数式通信方式。通过详细的对比表格,清晰展示 Vue 2 Vue 3 在各种通信机制上的具体差异,有助于快速掌握迁移要点。


一、通信机制概览

Vue 的组件通信主要分为以下几类:

  1. 事件通信:$emit/$on/$off、事件总线、自定义事件
  2. 父子组件通信:props/emit$attrs/$listeners$refs
  3. 跨层级通信:provide/inject$parent/$children

 

核心差异概览

Vue 3 通信机制的主要变化:

变化类型

具体内容

影响范围

API 移除

$on/$off/$once 完全移除

事件总线模式

API 移除

$children 移除

子组件批量访问

API 移除

$listeners 独立存在移除

属性和事件透传

API 合并

$listeners 合并到 $attrs

组件透传简化

使用调整

emit 需先定义再使用

Composition API

函数化改造

provide/inject 变为函数

跨层级通信

函数化改造

ref() 替代 this.$refs

模板 ref

语法统一

v-model:xxx 替代 .sync

双向绑定

 

三、详细对比

以下表格详细列出了事件通信和组件通信的所有核心差异,包括特性说明、具体实现方式和完整代码示例。

内容特性

Vue 2 特性

Vue 2 案例

Vue 3 特性

Vue 3 案例

事件监听
$on

Vue 实例自带事件监听方法
可直接使用 this.$on()

// 全局 EventBus
Vue.prototype.$bus = new Vue()

// 组件监听
this.$bus.$on('user-login', (data) => {
  console.log(data)
})

移除 $on/$off/$once
需使用第三方库(mitt

// utils/eventBus.js
import mitt from 'mitt'
const emitter = mitt()
export default emitter

// 组件监听
emitter.on('user-login', (data) => {
  console.log(data)
})

事件取消
$off

Vue 实例自带事件取消方法
可直接使用 this.$off()

// 清理事件监听
beforeDestroy() {
  this.$bus.$off('user-login', handler)
}

移除 $off
使用 mitt.off()

// 清理事件监听
onUnmounted(() => {
  emitter.off('user-login', handler)
})

单次监听
$once

Vue 实例自带单次监听方法
监听一次后自动取消

this.$bus.$once('init', (data) => {
  console.log('只触发一次', data)
})

移除 $once
需手动实现(on + off

const handler = (data) => {
  console.log('只触发一次', data)
  emitter.off('init', handler)
}
emitter.on('init', handler)

触发事件
$emit

Options API 中直接调用
无需预先声明
this.$emit()

export default {
  methods: {
    send() {
      this.$emit('change', this.value)
    }
  }
}

Composition API 需先定义
使用 defineEmits 声明
setup() 中不能用 this.$emit

<script setup>
const emit = defineEmits(['change'])

const send = () => {
  emit('change', value)
}
</script>

事件总线
EventBus

使用 Vue 实例作为事件总线
new Vue() 自带所有事件方法

// main.js
Vue.prototype.$bus = new Vue()

// Component A
this.$bus.$emit('event', data)

// Component B
this.$bus.$on('event', handler)

移除 Vue 实例事件方法
推荐 mitt 库(轻量 200 bytes

// eventBus.js
import mitt from 'mitt'
const emitter = mitt()
export default emitter

// Component A
emitter.emit('event', data)

// Component B
emitter.on('event', handler)

父子通信
Props

使用 props 选项
数组或对象形式

export default {
  props: ['value', 'config'],
  data() {
    return {
      local: this.value
    }
  }
}

使用 defineProps 函数
Composition API 函数式

<script setup>
const props = defineProps(['value', 'config'])

const local = ref(props.value)
</script>

父子通信
Emit

直接调用 this.$emit()
无需声明事件

export default {
  methods: {
    update() {
      this.$emit('update:value', newValue)
    }
  }
}

必须先声明事件
使用 defineEmits

<script setup>
const emit = defineEmits(['update:value'])

const update = () => {
  emit('update:value', newValue)
}
</script>

属性透传
$attrs/$listeners

$attrs $listeners 分离
属性和事件分别透传

<template>
  <Child
    v-bind="$attrs"
    v-on="$listeners" />
</template>

<script>
export default {
  inheritAttrs: false
}
</script>

$listeners 合并到 $attrs
统一透传属性和事件

<template>
  <Child v-bind="$attrs" />
</template>

<script setup>
inheritAttrs: false
</script>

跨层级通信
Provide

provide 选项或函数
返回对象
数据默认非响应式

export default {
  provide() {
    return {
      theme: this.theme,  // 非响应式
      getUser: () => this.user  // 函数式
    }
  }
}

provide() 函数
配合 ref/reactive
天然响应式

<script setup>
import { ref, provide } from 'vue'

const theme = ref('dark')
provide('theme', theme)  // 响应式
</script>

跨层级通信
Inject

inject 选项
数组或对象形式
接收非响应式数据

export default {
  inject: ['theme', 'getUser'],
  computed: {
    user() {
      return this.getUser()  // 手动响应式
    }
  }
}

inject() 函数
直接接收响应式数据

<script setup>
import { inject } from 'vue'

const theme = inject('theme')  // 响应式
const user = inject('user', 'default')
</script>

子组件访问
$children

this.$children 数组
包含所有直接子组件
可遍历批量操作

export default {
  mounted() {
    this.$children.forEach(child => {
      child.reset()
    })
  }
}

移除 $children
建议使用 ref provide/inject

<script setup>
const child1 = ref()
const child2 = ref()

onMounted(() => {
  child1.value?.reset()
  child2.value?.reset()
})
</script>

<template>
  <Child1 ref="child1" />
  <Child2 ref="child2" />
</template>

模板引用
$refs

字符串 ref
通过 this.$refs 访问

<template>
  <input ref="inputRef" />
</template>

<script>
export default {
  mounted() {
    this.$refs.inputRef.focus()
  }
}
</script>

ref() 响应式变量
变量名必须与模板 ref 一致

<script setup>
import { ref, onMounted } from 'vue'

const inputRef = ref(null)

onMounted(() => {
  inputRef.value?.focus()
})
</script>

<template>
  <input ref="inputRef" />
</template>

双向绑定
.sync

.sync 修饰符
:value.sync="data"

<template>
  <Dialog :visible.sync="show" />
</template>

<script>
export default {
  data() {
    return { show: false }
  }
}
</script>

<!-- Dialog.vue -->
<script>
export default {
  props: ['visible'],
  methods: {
    close() {
      this.$emit('update:visible', false)
    }
  }
}
</script>

v-model:xxx 统一语法
替代 .sync

<template>
  <Dialog v-model:visible="show" />
</template>

<script setup>
import { ref } from 'vue'
const show = ref(false)
</script>

<!-- Dialog.vue -->
<script setup>
const props = defineProps(['visible'])
const emit = defineEmits(['update:visible'])

const close = () => {
  emit('update:visible', false)
}
</script>

默认双向绑定
v-model

默认 prop value
事件为 input

<template>
  <Input v-model="text" />
</template>

<!-- Input.vue -->
<script>
export default {
  props: ['value'],
  methods: {
    handleInput(e) {
      this.$emit('input', e.target.value)
    }
  }
}
</script>

默认 prop modelValue
事件为 update:modelValue

<template>
  <Input v-model="text" />
</template>

<!-- Input.vue -->
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])

const handleInput = (e) => {
  emit('update:modelValue', e.target.value)
}
</script>

TypeScript 支持

类型定义较弱
需手动声明类型

export default {
  props: {
    value: String,
    count: Number
  }
}

完善的类型推断
自动类型检查

<script setup lang="ts">
interface Props {
  value: string
  count?: number
}

interface Emits {
  (e: 'update:value', val: string): void
  (e: 'change', data: object): void
}

const props = defineProps<Props>()
const emit = defineEmits<Emits>()
</script>

响应式处理
provide/inject

默认非响应式
需手动处理响应式

export default {
  data() {
    return { theme: 'dark' }
  },
  provide() {
    return {
      // 非响应式
      theme: this.theme,
      // 函数式响应式
      getTheme: () => this.theme
    }
  }
}

天然响应式
ref/reactive 自动传递

<script setup>
import { ref, provide, inject } from 'vue'

const theme = ref('dark')
provide('theme', theme)

// 子组件
const theme = inject('theme')
// 自动响应式,theme.value 更新
</script>

组件方法暴露
defineExpose

可通过 $children $refs
访问组件所有方法和数据

export default {
  methods: {
    reset() {
      this.data = ''
    }
  }
}

// 父组件
this.$refs.child.reset()

Composition API 默认封闭
需使用 defineExpose 显式暴露

<script setup>
const data = ref('value')

const reset = () => {
  data.value = ''
}

// 显式暴露给父组件
defineExpose({
  reset
})
</script>

// 父组件
<script setup>
const child = ref()
child.value?.reset()
</script>

 

四、实际应用示例

以下通过完整示例展示 Vue 2 Vue 3 中各种通信方式的实现:

示例1:父子组件通信(props + emit

Vue 2 Options API

<!-- Parent.vue -->
<template>
  <Child :value="count" @update="handleUpdate" />
</template>

<script>
export default {
  data() {
    return { count: 0 }
  },
  methods: {
    handleUpdate(val) {
      this.count = val
    }
  }
}
</script>

<!-- Child.vue -->
<script>
export default {
  props: ['value'],
  methods: {
    increment() {
      this.$emit('update', this.value + 1)
    }
  }
}
</script>

Vue 3 Composition API

<!-- Parent.vue -->
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'

const count = ref(0)
const handleUpdate = (val) => {
  count.value = val
}
</script>

<template>
  <Child :value="count" @update="handleUpdate" />
</template>

<!-- Child.vue -->
<script setup>
const props = defineProps(['value'])
const emit = defineEmits(['update'])

const increment = () => {
  emit('update', props.value + 1)
}
</script>

示例2:跨层级通信

Vue 2

export default {
  provide() {
    return {
      theme: this.theme
    }
  },
  inject: ['theme']
}

Vue 3

<script setup>
import { ref, provide, inject } from 'vue'

// 父组件提供数据
const theme = ref('dark')
provide('theme', theme)

// 子组件注入数据
const currentTheme = inject('theme')
</script>

示例3:事件总线替代方案

Vue 2EventBus):

// main.js
Vue.prototype.$bus = new Vue()

// Component A
this.$bus.$emit('user-login', userData)

// Component B
this.$bus.$on('user-login', (data) => {
  console.log(data)
})

Vue 3mitt库):

// eventBus.js
import mitt from 'mitt'
const emitter = mitt()
export default emitter

// Component A
import emitter from './eventBus'
emitter.emit('user-login', userData)

// Component B
import emitter from './eventBus'
emitter.on('user-login', (data) => {
  console.log(data)
})

示例4:模板 ref 通信

Vue 2

<template>
  <input ref="inputRef" />
</template>

<script>
export default {
  mounted() {
    this.$refs.inputRef.focus()
  }
}
</script>

Vue 3

<script setup>
import { ref, onMounted } from 'vue'

const inputRef = ref(null)

onMounted(() => {
  inputRef.value.focus()
})
</script>

<template>
  <input ref="inputRef" />
</template>

 

五、最佳实践建议

在实际开发中,合理选择通信方式能提升代码质量和可维护性:

1、父子组件通信优先级

  • 简单数据传递:使用 props + emit(最标准)
  • 复杂表单/组件:使用 v-model:xxxVue 3 推荐)
  • 深层属性透传:使用 $attrs + inheritAttrs: false

2、跨层级通信选择

  • 3层以内:优先 props/emit 逐层传递(数据流清晰)
  • 深层嵌套:使用 provide/inject(避免 prop drilling
  • 兄弟组件:优先状态管理,必要时使用事件库

3、Composition API 通信优势

  • defineProps/defineEmits 自动类型推断
  • provide/inject 支持响应式数据
  • 逻辑复用更简单(Composables

4、避免通信陷阱

  • 不要过度使用 provide/inject(数据流不透明)
  • 不要滥用 ref 直接操作子组件(耦合度高)
  • 移除 this.$children 后,优先响应式方案

5、事件总线替代建议

  • Vue 3 移除了 $on/$off,推荐 mitt 库(轻量)
  • 事件名建议常量定义,避免冲突
  • 记得在 onUnmounted off 清理监听

6、TypeScript 支持

  • Vue 3 defineProps/defineEmits 可定义类型
  • provide/inject 支持 InjectionKey 类型安全
  • 事件总线建议使用泛型定义事件类型

7、响应式通信注意

  • provide 的数据建议用 ref/reactive 包装
  • inject 接收时保持响应式,避免解构丢失
  • 可配合 readonly 实现单向数据流

 

总结

Vue 3 在通信机制上的改进体现了框架演进的方向:

1. 从实例方法到函数式 API - 更灵活、可复用、类型安全

2. 从分离 API 到统一 API - 更简洁、一致($listeners 合并到 $attrs

3. 从隐式访问到显式声明 - 更清晰、规范(emit 需定义,defineExpose 需暴露)

4. 从手动响应式到天然响应式 - provide/inject 自动响应式传递

 

posted on 2026-06-15 23:40  JustItIs  阅读(2)  评论(0)    收藏  举报

导航