Vue 2 有哪些组件通信方式?哪些写法在 Vue 3 中被移除或改变?
Vue 2 用 props/$emit、.sync、$attrs/$listeners、$parent/$refs、provide/inject、EventBus 和 Vuex 通信。Vue 3 中 $listeners 并入 $attrs,.sync 改为 v-model:xxx,$on/$off/$once 被移除,EventBus 需改用 mitt 等库。
参考答案
Vue 2 已于 2023-12-31 停止维护,本题 Vue 2 部分以 2.7 为准。props 单向数据流、插槽与 provide/inject 的取舍分别见《为什么 Vue 提倡 props 单向数据流?》和《插槽、作用域插槽和 provide/inject 各解决什么问题?》。
简答
- 父子之间首选 props 向下、
$emit向上;需要“双向”时用v-model或.sync语法糖,本质仍是 prop + 事件。 - 透传用
$attrs和$listeners;跨多层用provide/inject;全局共享状态用 Vuex;$parent、$refs直接访问实例,耦合最强。 - Vue 3 中
$listeners被移除,监听器以onXxx形式并入$attrs;$attrs也开始包含class和style。 - Vue 3 移除了
.sync和model选项,改为v-model:title;组件v-model默认的 prop/事件从value/input变为modelValue/update:modelValue。 - Vue 3 移除了实例上的
$on、$off、$once,基于new Vue()的 EventBus 不再可用,官方建议改用 mitt、tiny-emitter 等库;官方状态管理推荐也从 Vuex 变为 Pinia。
Vue 2 的通信方式
| 方式 | 方向 | 说明 |
|---|---|---|
props / $emit | 父 → 子 / 子 → 父 | 最常用,数据流清晰 |
v-model / .sync | 双向语法糖 | :title.sync="t" 等价于 :title="t" @update:title="t = $event" |
$attrs / $listeners | 透传 | 包装组件把未声明的属性和监听器转交给内部元素 |
$parent / $children / $refs | 直接访问实例 | 便捷但耦合重,适合调用子组件方法 |
| provide / inject | 祖先 → 后代 | Vue 2 中注入的值默认不是响应式,除非提供的是响应式对象 |
| EventBus | 任意组件 | const bus = new Vue(),用 $on/$emit/$off |
| Vuex | 全局 | 集中管理共享状态 |
Vue 3 的变化
$listeners 并入 $attrs
Vue 3 的虚拟 DOM 中,事件监听器就是带 on 前缀的属性,因此归入 $attrs,$listeners 被移除。
vue
<!-- Vue 2 -->
<input v-bind="$attrs" v-on="$listeners" />
<!-- Vue 3 -->
<input v-bind="$attrs" />
两个版本都可以配合 inheritAttrs: false 控制透传位置。注意 Vue 3 的 $attrs 包含 class 和 style,关闭 inheritAttrs 后它们也不会自动落到根元素上。
.sync 改为 v-model 参数
vue
<!-- Vue 2 -->
<ChildComponent :title.sync="pageTitle" />
<!-- Vue 3 -->
<ChildComponent v-model:title="pageTitle" />
子组件仍然 emit('update:title', value)。Vue 3 允许同一组件绑定多个 v-model,组件的 model 选项也被移除。
$on / $off / $once 移除
组件实例不再实现事件发射器接口,EventBus 写法需要替换:
JavaScript
// Vue 3:用 mitt 替代 new Vue() 作为事件总线
import mitt from 'mitt';
export const bus = mitt();
bus.on('saved', handler);
bus.emit('saved', payload);
bus.off('saved', handler);
迁移指南同时提醒,大多数情况下不建议用全局事件总线,优先考虑 props/事件、provide/inject 或全局状态管理。$emit 本身仍保留,用于触发父组件监听的事件。
其他
$children被移除,改用模板 ref 访问子组件。- Vuex 4 仍支持 Vue 3,但官方文档说明 Vuex 已进入维护模式,新项目推荐 Pinia。
常见追问
- Vue 3 中子组件的
$emit事件需要声明吗?推荐用emits选项或defineEmits声明;未声明的监听器会留在$attrs中,默认透传到子组件根元素。 - 迁移时
.sync和$listeners能临时保留吗?@vue/compat迁移构建提供COMPONENT_V_MODEL、INSTANCE_LISTENERS、INSTANCE_EVENT_EMITTER等兼容开关,可以逐步替换。