系统性整理组件传参14种方式

158 阅读2分钟

标题前言:

在面试时被问到组件传参的方式没有答的很完整全面,在经过很多面试之后发现,面试的回答已经不在于你是否答出来,更高的一个level是要答全,答出其他面试者答不出来的,得有自己的一个框架,于是系统性整理了14种方式 系统化梳理每一种方式的原理、用法、适用场景、优缺点及注意事项,并标注其在 Vue 2 vs Vue 3 中的支持情况,帮助你全面掌握。


📚 Vue 组件传参与通信方式全解析(14 种)

✅ 表示推荐 / 安全
⚠️ 表示谨慎使用
❌ 表示已废弃 / 不推荐


1. props(✅ 推荐)

  • 方向:父 → 子
  • 原理:声明式属性传递,单向数据流
  • Vue 2/3:✅ 全支持
  • 示例
    <!-- 父 -->
    <Child :title="msg" />
    <!-- 子 -->
    defineProps({ title: String })
    
  • 优点:清晰、类型安全、可预测
  • 注意:不要直接修改 prop(Vue 会警告)

2. $emit / v-on(✅ 推荐)

  • 方向:子 → 父
  • 原理:子组件触发自定义事件,父组件监听
  • Vue 2this.$emit('event', data)
  • Vue 3const emit = defineEmits(['event'])
  • 示例
    <!-- 子 -->
    emit('update', newValue)
    <!-- 父 -->
    <Child @update="handle" />
    
  • 优点:解耦、符合事件驱动思想

3. .sync 修饰符(⚠️ Vue 2 专用,Vue 3 已移除)

  • 原理:语法糖,等价于 :prop + @update:prop
  • Vue 2 示例
    <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:propName
    <Child v-model:name="userName" v-model:age="userAge" />
    <!-- 子组件需 emit('update:name', ...) -->
    
  • 优点:简洁、语义清晰,适合表单控件

5. ref(✅ 有限推荐)

  • 方向:父 → 子(获取子实例或 DOM)
  • 原理:通过 ref 引用子组件实例,直接调用方法或访问数据
  • 示例
    <Child ref="childRef" />
    // 父组件中:this.$refs.childRef.doSomething()(Vue 2)
    // Vue 3:const childRef = ref(); childRef.value.doSomething()
    
  • 适用场景:调用子组件方法(如 focus、validate)
  • ⚠️ 注意:破坏封装性,应避免读写子组件内部状态

6. children/children / parent(❌ 不推荐)

  • 原理:直接访问父子组件实例
  • 问题
    • $children 顺序不确定
    • 破坏组件独立性
    • 难以维护和测试
  • Vue 3:❌ $children 已移除,$parent 仍存在但不鼓励使用
  • 替代方案:props / emits / provide-inject

7. attrs/attrs / listeners(✅ Vue 2;Vue 3 合并为 $attrs

  • 用途:透传未声明的 props 和事件(常用于高阶组件、包装组件)
  • Vue 2
    • $attrs:未被 props 声明的 attribute
    • $listeners:所有 v-on 事件监听器
  • Vue 3$listeners 被合并进 $attrs(包含 onXxx 事件)
  • 典型用法:封装第三方 UI 组件
    <!-- Wrapper.vue -->
    <el-input v-bind="$attrs" v-on="$listeners" />
    

8. provide / inject(✅ 推荐)

  • 方向:祖先 → 后代(跨多层)
  • 原理:依赖注入,类似 React Context
  • 响应式:需传递 refreactive 对象
    // 祖先
    provide('theme', themeRef)
    // 后代
    const theme = inject('theme')
    
  • 适用:主题、语言、用户信息等全局配置
  • Vue 2/3:✅ 支持(Vue 3 更简洁)

9. EventBus(事件总线)(⚠️ 谨慎使用)

  • 原理:基于发布-订阅模式的全局通信
  • 实现
    • Vue 2:new Vue() 作事件中心
    • Vue 3:需引入 mitt 等库
  • 问题
    • 难以追踪数据流
    • 容易内存泄漏(忘记 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" />
    • 作用域插槽(关键!):子 → 父传数据
      <!-- 子 -->
      <slot :user="currentUser" />
      <!-- 父 -->
      <Child v-slot="{ user }">{{ user.name }}</Child>
      
  • 适用:高度可定制组件(表格、弹窗、卡片)

13. sessionStorage / localStorage(⚠️ 特定场景)

  • 原理:通过浏览器存储实现“伪通信”
  • 适用场景
    • 页面刷新后保持状态
    • 多 Tab 间简单同步(配合 storage 事件)
  • 缺点
    • 非响应式(需手动监听 storage 事件)
    • 数据类型限制(仅字符串)
    • 不适合实时通信
  • 建议:仅用于持久化,非组件通信首选

14. postMessage(⚠️ 跨文档/跨域通信)

  • 原理:HTML5 提供的安全跨域通信机制
  • 适用场景
    • iframe 与主页面通信
    • Web Worker 与主线程
    • 跨域窗口通信
  • 示例
    // 主页面
    iframe.contentWindow.postMessage(data, '*')
    window.addEventListener('message', handler)
    
  • 注意:需验证 event.origin 防止 XSS
  • 与组件通信关系:属于跨上下文通信,非组件内部机制

📊 总结对比表

方式方向Vue 2Vue 3推荐度适用场景
props父→子✅✅✅基础数据传递
$emit / v-on子→父✅✅✅事件通知
.sync双向⚠️Vue 2 双向绑定(已淘汰)
v-model双向✅(单)✅(多)✅✅✅表单、可编辑组件
ref父→子(调用)✅✅调用子方法
children/children/parent双向❌/$parent——
attrs/attrs/listeners透传✅(合并)✅✅高阶组件封装
provide/inject祖先→后代✅✅✅跨层级配置
EventBus任意需 mitt⚠️小型项目临时通信
Vuex/Pinia全局✅/✅Pinia✅✅✅✅复杂状态共享
$root全局✅(不推荐)——
slot父→子(内容)✅✅✅UI 定制
sessionStorage持久化⚠️刷新保活、多 Tab
postMessage跨上下文⚠️iframe、Worker

✅ 最佳实践建议

  1. 优先使用 props + emits:保持组件清晰。
  2. 跨层级用 provide/inject,而非 $parent
  3. 共享状态用 Pinia,而非 EventBus 或 $root
  4. 避免直接操作子组件(ref 仅用于方法调用)。
  5. 作用域插槽是高级组件设计的利器。
  6. localStorage / postMessage 属于特殊场景,勿滥用。