标题前言:
在面试时被问到组件传参的方式没有答的很完整全面,在经过很多面试之后发现,面试的回答已经不在于你是否答出来,更高的一个level是要答全,答出其他面试者答不出来的,得有自己的一个框架,于是系统性整理了14种方式
系统化梳理每一种方式的原理、用法、适用场景、优缺点及注意事项 ,并标注其在 Vue 2 vs Vue 3 中的支持情况,帮助你全面掌握。
📚 Vue 组件传参与通信方式全解析(14 种)
✅ 表示推荐 / 安全
⚠️ 表示谨慎使用
❌ 表示已废弃 / 不推荐
1. props(✅ 推荐)
-
方向:父 → 子
-
原理:声明式属性传递,单向数据流
-
Vue 2/3:✅ 全支持
-
示例 :
vue<!-- 父 --> <Child :title="msg" /> <!-- 子 --> defineProps({ title: String }) -
优点:清晰、类型安全、可预测
-
注意:不要直接修改 prop(Vue 会警告)
2. $emit / v-on(✅ 推荐)
-
方向:子 → 父
-
原理:子组件触发自定义事件,父组件监听
-
Vue 2 :
this.$emit('event', data) -
Vue 3 :
const emit = defineEmits(['event']) -
示例 :
vue<!-- 子 --> emit('update', newValue) <!-- 父 --> <Child @update="handle" /> -
优点:解耦、符合事件驱动思想
3. .sync 修饰符(⚠️ Vue 2 专用,Vue 3 已移除)
-
原理 :语法糖,等价于
:prop + @update:prop -
Vue 2 示例 :
vue<Child :title.sync="pageTitle" /> <!-- 子组件需 emit('update:title', newTitle) --> -
Vue 3 :❌ 不支持,改用
v-model:propName -
替代方案 :多
v-model(见第 4 条)
4. v-model(✅ 推荐,Vue 3 增强)
-
原理:双向绑定语法糖
-
Vue 2 :仅支持单个
value+input事件 -
Vue 3 :支持多个
v-model:propNamevue<Child v-model:name="userName" v-model:age="userAge" /> <!-- 子组件需 emit('update:name', ...) --> -
优点:简洁、语义清晰,适合表单控件
5. ref(✅ 有限推荐)
-
方向:父 → 子(获取子实例或 DOM)
-
原理 :通过
ref引用子组件实例,直接调用方法或访问数据 -
示例 :
vue<Child ref="childRef" /> // 父组件中:this.$refs.childRef.doSomething()(Vue 2) // Vue 3:const childRef = ref(); childRef.value.doSomething() -
适用场景:调用子组件方法(如 focus、validate)
-
⚠️ 注意:破坏封装性,应避免读写子组件内部状态
6. children / parent(❌ 不推荐)
- 原理:直接访问父子组件实例
- 问题 :
$children顺序不确定- 破坏组件独立性
- 难以维护和测试
- Vue 3 :❌
$children已移除,$parent仍存在但不鼓励使用 - 替代方案:props / emits / provide-inject
7. **$attrs / listeners∗∗(✅Vue2;Vue3合并为'listeners**(✅ Vue 2;Vue 3 合并为 `listeners∗∗(✅Vue2;Vue3合并为'attrs`)
-
用途:透传未声明的 props 和事件(常用于高阶组件、包装组件)
-
Vue 2 :
$attrs:未被 props 声明的 attribute$listeners:所有v-on事件监听器
-
Vue 3 :
$listeners被合并进$attrs(包含onXxx事件) -
典型用法 :封装第三方 UI 组件
vue<!-- Wrapper.vue --> <el-input v-bind="$attrs" v-on="$listeners" />
8. provide / inject(✅ 推荐)
-
方向:祖先 → 后代(跨多层)
-
原理:依赖注入,类似 React Context
-
响应式 :需传递
ref或reactive对象js// 祖先 provide('theme', themeRef) // 后代 const theme = inject('theme') -
适用:主题、语言、用户信息等全局配置
-
Vue 2/3:✅ 支持(Vue 3 更简洁)
9. EventBus(事件总线)(⚠️ 谨慎使用)
- 原理:基于发布-订阅模式的全局通信
- 实现 :
- Vue 2:
new Vue()作事件中心 - Vue 3:需引入
mitt等库
- Vue 2:
- 问题 :
- 难以追踪数据流
- 容易内存泄漏(忘记 off)
- 不利于大型项目维护
- 建议 :仅用于小型项目或临时解耦,优先用 Pinia
10. Vuex / Pinia(状态管理)(✅ 大型项目推荐)
- 原理:集中式状态管理
- Vuex:Vue 2 官方方案(较重)
- Pinia:Vue 3 官方推荐(更轻量、TypeScript 友好)
- 优点 :
- 状态可预测
- 支持 DevTools 调试
- 逻辑复用(actions/getters)
- 适用:多组件共享状态、持久化、复杂业务逻辑
11. $root(❌ 不推荐)
- 原理 :访问根实例(
new Vue()) - 问题 :
- 全局耦合
- 难以测试和维护
- 在组件库或微前端中不可靠
- Vue 3 :❌
$root仍存在但强烈不建议使用 - 替代:provide/inject 或 Pinia
12. slot(插槽)(✅ 推荐)
-
方向:父 → 子(内容分发)
-
类型 :
-
默认插槽
<slot /> -
具名插槽
<slot name="header" /> -
作用域插槽 (关键!):子 → 父传数据
vue<!-- 子 --> <slot :user="currentUser" /> <!-- 父 --> <Child v-slot="{ user }">{{ user.name }}</Child>
-
-
适用:高度可定制组件(表格、弹窗、卡片)
13. sessionStorage / localStorage(⚠️ 特定场景)
- 原理:通过浏览器存储实现"伪通信"
- 适用场景 :
- 页面刷新后保持状态
- 多 Tab 间简单同步(配合
storage事件)
- 缺点 :
- 非响应式(需手动监听
storage事件) - 数据类型限制(仅字符串)
- 不适合实时通信
- 非响应式(需手动监听
- 建议 :仅用于持久化,非组件通信首选
14. postMessage(⚠️ 跨文档/跨域通信)
-
原理:HTML5 提供的安全跨域通信机制
-
适用场景 :
- iframe 与主页面通信
- Web Worker 与主线程
- 跨域窗口通信
-
示例 :
js// 主页面 iframe.contentWindow.postMessage(data, '*') window.addEventListener('message', handler) -
注意 :需验证
event.origin防止 XSS -
与组件通信关系 :属于跨上下文通信,非组件内部机制
📊 总结对比表
| 方式 | 方向 | Vue 2 | Vue 3 | 推荐度 | 适用场景 |
|---|---|---|---|---|---|
| props | 父→子 | ✅ | ✅ | ✅✅✅ | 基础数据传递 |
| $emit / v-on | 子→父 | ✅ | ✅ | ✅✅✅ | 事件通知 |
| .sync | 双向 | ✅ | ❌ | ⚠️ | Vue 2 双向绑定(已淘汰) |
| v-model | 双向 | ✅(单) | ✅(多) | ✅✅✅ | 表单、可编辑组件 |
| ref | 父→子(调用) | ✅ | ✅ | ✅✅ | 调用子方法 |
| children/children/children/parent | 双向 | ✅ | ❌/$parent | ❌ | ------ |
| attrs/attrs/attrs/listeners | 透传 | ✅ | ✅(合并) | ✅✅ | 高阶组件封装 |
| provide/inject | 祖先→后代 | ✅ | ✅ | ✅✅✅ | 跨层级配置 |
| EventBus | 任意 | ✅ | 需 mitt | ⚠️ | 小型项目临时通信 |
| Vuex/Pinia | 全局 | ✅/✅ | Pinia✅ | ✅✅✅ | 复杂状态共享 |
| $root | 全局 | ✅ | ✅(不推荐) | ❌ | ------ |
| slot | 父→子(内容) | ✅ | ✅ | ✅✅✅ | UI 定制 |
| sessionStorage | 持久化 | ✅ | ✅ | ⚠️ | 刷新保活、多 Tab |
| postMessage | 跨上下文 | ✅ | ✅ | ⚠️ | iframe、Worker |
✅ 最佳实践建议
- 优先使用 props + emits:保持组件清晰。
- 跨层级用 provide/inject ,而非
$parent。 - 共享状态用 Pinia ,而非 EventBus 或
$root。 - 避免直接操作子组件(ref 仅用于方法调用)。
- 作用域插槽是高级组件设计的利器。
- localStorage / postMessage 属于特殊场景,勿滥用。