目录

一、父子组件通信(6种方式)

1. Props(父 → 子)

2. Emit(子 → 父)

3. v-model(双向绑定)⭐ 变化最大

4. $refs(获取子组件实例)

5. parent/parent/children

6. attrs/attrs/listeners(属性透传)

二、兄弟/平级组件通信(3种方式)

7. 共同父组件中转

8. EventBus(事件总线)

9. Vuex / Pinia(状态管理)

三、跨层级组件通信(2种方式)

10. Provide / Inject

11. $root(访问根实例)

四、全局通信(2种方式)

12. Vuex(Vue2 主流)

13. Pinia(Vue3 官方推荐)

五、特殊场景通信(3种方式)

14. Slot(插槽通信)

15. Teleport(Vue3 新增)

16. 动态组件通信

六、完整对比总结表

七、面试怎么回答才精彩

🎯 初级回答(60分)

🎯 中级回答(80分)

🎯 高级回答(95分)✨

🔥 加分项

💡 面试官可能的追问

一、父子组件通信(6种方式)

1. Props(父 → 子)

Vue2 实现

// 父组件
<template>
  <Child :user="userInfo" :count="10" />
</template>

<script>
export default {
  data() {
    return {
      userInfo: { name: 'Alice', age: 25 }
    }
  }
}
</script>
// 子组件
<script>
export default {
  props: {
    user: {
      type: Object,
      required: true,
      default: () => ({})
    },
    count: {
      type: Number,
      validator: (val) => val >= 0
    }
  },
  mounted() {
    console.log(this.user.name)  // 通过 this 访问
  }
}
</script>

Vue3 实现

// Options API(与 Vue2 几乎相同)
export default {
  props: {
    user: Object,
    count: Number
  }
}
<!-- Composition API(<script setup>) -->
<script setup>
// 方式1:运行时声明
const props = defineProps({
  user: {
    type: Object,
    required: true
  },
  count: Number
})

// 方式2:TypeScript 类型声明(推荐)
const props = defineProps<{
  user: { name: string; age: number }
  count?: number
}>()

// 方式3:带默认值的 TS 声明
interface Props {
  user: User
  count?: number
}
const props = withDefaults(defineProps<Props>(), {
  count: 0
})

// 访问 props
console.log(props.user.name)  // 不需要 this
</script>

关键差异:

  • Vue3 的 defineProps 是编译时宏,无需 import
  • <script setup> 中无 this,直接用 props.xxx
  • TypeScript 支持更强大

2. Emit(子 → 父)

Vue2 实现

// 子组件
<template>
  <button @click="handleClick">提交</button>
</template>

<script>
export default {
  methods: {
    handleClick() {
      // 触发自定义事件
      this.$emit('submit', { id: 1, data: 'test' })
      this.$emit('update:visible', false)
    }
  }
}
</script>
// 父组件
<template>
  <Child @submit="onSubmit" @update:visible="visible = $event" />
</template>

<script>
export default {
  methods: {
    onSubmit(payload) {
      console.log(payload)
    }
  }
}
</script>

Vue3 实现

<!-- 子组件 -->
<script setup>
// 方式1:数组声明
const emit = defineEmits(['submit', 'update:visible'])

// 方式2:对象声明(带校验)
const emit = defineEmits({
  submit: (payload) => {
    return payload.id > 0  // 返回 false 会警告
  },
  'update:visible': null
})

// 方式3:TypeScript 声明
const emit = defineEmits<{
  (e: 'submit', payload: { id: number; data: string }): void
  (e: 'update:visible', value: boolean): void
}>()

const handleClick = () => {
  emit('submit', { id: 1, data: 'test' })
}
</script>

关键差异:

  • Vue3 强制要求声明 emits(开发模式会警告)
  • defineEmits 返回 emit 函数,不再用 this.$emit
  • 支持 TypeScript 类型约束

3. v-model(双向绑定)⭐ 变化最大

Vue2 实现

// 默认 v-model(绑定 value + input)
// 父组件
<CustomInput v-model="searchText" />
// 等价于
<CustomInput :value="searchText" @input="searchText = $event" />

// 子组件
export default {
  props: ['value'],
  methods: {
    handleInput(e) {
      this.$emit('input', e.target.value)
    }
  }
}
// 自定义 v-model
export default {
  model: {
    prop: 'checked',
    event: 'change'
  },
  props: ['checked'],
  methods: {
    toggle() {
      this.$emit('change', !this.checked)
    }
  }
}
<!-- 多个双向绑定用 .sync -->
<Dialog :visible.sync="show" :title.sync="dialogTitle" />
<!-- 等价于 -->
<Dialog 
  :visible="show" 
  @update:visible="show = $event"
  :title="dialogTitle"
  @update:title="dialogTitle = $event"
/>

Vue3 实现

<!-- 默认 v-model(绑定 modelValue) -->
<!-- 父组件 -->
<CustomInput v-model="searchText" />
<!-- 等价于 -->
<CustomInput 
  :modelValue="searchText" 
  @update:modelValue="searchText = $event" 
/>

<!-- 子组件 -->
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])

const handleInput = (e) => {
  emit('update:modelValue', e.target.value)
}
</script>
<!-- 多个 v-model(取代 .sync) -->
<!-- 父组件 -->
<Dialog v-model:visible="show" v-model:title="dialogTitle" />

<!-- 子组件 -->
<script setup>
defineProps(['visible', 'title'])
const emit = defineEmits(['update:visible', 'update:title'])

const close = () => emit('update:visible', false)
const changeTitle = (val) => emit('update:title', val)
</script>
<!-- 自定义修饰符 -->
<CustomInput v-model.capitalize="text" />

<script setup>
const props = defineProps({
  modelValue: String,
  modelModifiers: { default: () => ({}) }
})

const emit = defineEmits(['update:modelValue'])

const emitValue = (val) => {
  if (props.modelModifiers.capitalize) {
    val = val.charAt(0).toUpperCase() + val.slice(1)
  }
  emit('update:modelValue', val)
}
</script>

对比表格:

特性 Vue2 Vue3
默认 prop value modelValue
默认事件 input update:modelValue
自定义 model 选项 具名 v-model
多个绑定 .sync 修饰符 v-model:xxx
自定义修饰符 ❌ 不支持 ✅ 支持

4. $refs(获取子组件实例)

Vue2 实现

<!-- 父组件 -->
<template>
  <Child ref="childRef" />
  <button @click="callChild">调用子组件方法</button>
</template>

<script>
export default {
  methods: {
    callChild() {
      // 可以访问子组件所有数据和方法
      this.$refs.childRef.childMethod()
      console.log(this.$refs.childRef.childData)
    }
  }
}
</script>
// 子组件
export default {
  data() {
    return {
      childData: 'secret'
    }
  },
  methods: {
    childMethod() {
      console.log('child method called')
    }
  }
}

Vue3 实现

<!-- 父组件 Options API -->
<template>
  <Child ref="childRef" />
</template>

<script>
export default {
  mounted() {
    this.$refs.childRef.exposedMethod()
  }
}
</script>
<!-- 父组件 Composition API -->
<template>
  <Child ref="childRef" />
</template>

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

const childRef = ref(null)

onMounted(() => {
  childRef.value?.exposedMethod()
})
</script>
<!-- 子组件(<script setup>) -->
<script setup>
import { ref } from 'vue'

const privateData = ref('不能被访问')
const publicData = ref('可以被访问')

const privateMethod = () => {
  console.log('私有方法')
}

const publicMethod = () => {
  console.log('公开方法')
}

// ⚠️ 必须显式暴露
defineExpose({
  publicData,
  publicMethod
})
</script>

关键差异:

  • Vue2:父组件可访问子组件所有属性和方法
  • Vue3 <script setup>:默认完全封闭,必须用 defineExpose 暴露
  • 这是 Vue3 的重大设计变更,强化组件封装性

5. parent/parent/children

Vue2 实现

// 子组件访问父组件
this.$parent.parentMethod()
this.$parent.parentData

// 父组件访问子组件
this.$children[0].childMethod()
this.$children.forEach(child => {
  child.reset()
})

Vue3 实现

// $parent 仍可用(但不推荐)
this.$parent?.someMethod()

// ⚠️ $children 已完全移除
// 替代方案:
// 1. 使用 ref
// 2. 使用 provide/inject
// 3. 使用状态管理

迁移建议:

<!-- Vue2 代码 -->
<script>
export default {
  methods: {
    resetAllChildren() {
      this.$children.forEach(child => child.reset())
    }
  }
}
</script>

<!-- Vue3 替代方案 -->
<template>
  <Child ref="child1" />
  <Child ref="child2" />
</template>

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

const child1 = ref(null)
const child2 = ref(null)

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

6. attrs/attrs/listeners(属性透传)

Vue2 实现

<!-- 二次封装 Element UI 组件 -->
<template>
  <el-input
    v-bind="$attrs"
    v-on="$listeners"
    :value="value"
    @input="handleInput"
  />
</template>

<script>
export default {
  inheritAttrs: false,  // 禁止自动绑定到根元素
  props: ['value'],
  computed: {
    // $attrs: 未被 props 接收的属性
    // $listeners: 父组件绑定的事件监听器
  }
}
</script>

Vue3 实现

<!-- $listeners 已移除,事件合并到 $attrs -->
<template>
  <el-input
    v-bind="$attrs"
    :modelValue="modelValue"
    @update:modelValue="$emit('update:modelValue', $event)"
  />
</template>

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

defineOptions({
  inheritAttrs: false
})

defineProps(['modelValue'])
defineEmits(['update:modelValue'])

// Composition API 中获取 attrs
const attrs = useAttrs()
console.log(attrs)  // 包含属性和事件
</script>

对比:

项目 Vue2 Vue3
非 props 属性 $attrs $attrs
事件监听器 $listeners 合并到 $attrs
透传写法 v-bind="$attrs" v-on="$listeners" v-bind="$attrs"
Composition API useAttrs()

二、兄弟/平级组件通信(3种方式)

7. 共同父组件中转

<!-- 父组件 -->
<template>
  <ChildA @data-change="handleDataChange" />
  <ChildB :shared-data="sharedData" />
</template>

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

const sharedData = ref(null)

const handleDataChange = (data) => {
  sharedData.value = data
}
</script>

8. EventBus(事件总线)

Vue2 实现

// event-bus.js
import Vue from 'vue'
export const EventBus = new Vue()

// 或挂载到原型
Vue.prototype.$bus = new Vue()
// 组件 A(发送)
import { EventBus } from './event-bus'

export default {
  methods: {
    sendMessage() {
      EventBus.$emit('message', { text: 'Hello' })
    }
  }
}
// 组件 B(接收)
import { EventBus } from './event-bus'

export default {
  created() {
    EventBus.$on('message', this.handleMessage)
  },
  beforeDestroy() {
    EventBus.$off('message', this.handleMessage)  // ⚠️ 必须手动销毁
  },
  methods: {
    handleMessage(payload) {
      console.log(payload.text)
    }
  }
}

Vue3 实现

// ⚠️ Vue3 移除了实例的 $on、$off、$once 方法

// 方案1:使用 mitt 库
// npm install mitt

// event-bus.js
import mitt from 'mitt'
export const emitter = mitt()
<!-- 组件 A -->
<script setup>
import { emitter } from './event-bus'

const sendMessage = () => {
  emitter.emit('message', { text: 'Hello' })
}
</script>
<!-- 组件 B -->
<script setup>
import { onMounted, onUnmounted } from 'vue'
import { emitter } from './event-bus'

const handleMessage = (payload) => {
  console.log(payload.text)
}

onMounted(() => {
  emitter.on('message', handleMessage)
})

onUnmounted(() => {
  emitter.off('message', handleMessage)
})
</script>
// 方案2:使用 tiny-emitter
import { TinyEmitter } from 'tiny-emitter'
export const emitter = new TinyEmitter()

为什么 Vue3 移除了 EventBus?

  1. 容易造成隐式依赖,难以追踪数据流
  2. 忘记销毁监听器导致内存泄漏
  3. 官方推荐用 Pinia 或 Provide/Inject

9. Vuex / Pinia(状态管理)

见后文"全局通信"部分


三、跨层级组件通信(2种方式)

10. Provide / Inject

Vue2 实现

// 祖先组件
export default {
  provide() {
    return {
      theme: this.theme,           // ⚠️ 非响应式
      themeObject: this.themeObj,  // 对象引用可保持响应式
      getTheme: () => this.theme   // 通过函数保持响应式
    }
  },
  data() {
    return {
      theme: 'dark',
      themeObj: { color: 'dark' }
    }
  }
}
// 后代组件
export default {
  inject: ['theme', 'themeObject'],
  mounted() {
    console.log(this.theme)
  }
}

Vue2 响应式问题:

// ❌ 错误:provide 的基本类型不是响应式
provide() {
  return {
    count: this.count  // 后代拿到的是初始值,不会更新
  }
}

// ✅ 正确:提供整个响应式对象
provide() {
  return {
    state: this.$data  // 或者 Vue.observable({...})
  }
}

Vue3 实现

<!-- 祖先组件 -->
<script setup>
import { provide, ref, readonly, reactive } from 'vue'

// 方式1:提供响应式数据
const theme = ref('dark')
const config = reactive({ size: 'large' })

provide('theme', readonly(theme))  // 只读保护
provide('config', config)

// 方式2:提供修改方法
const updateTheme = (val) => {
  theme.value = val
}
provide('updateTheme', updateTheme)

// 方式3:使用 Symbol 作为 key(避免冲突)
const ThemeKey = Symbol()
provide(ThemeKey, theme)
</script>
<!-- 后代组件 -->
<script setup>
import { inject } from 'vue'

// 基本用法
const theme = inject('theme')
const updateTheme = inject('updateTheme')

// 带默认值
const theme = inject('theme', 'light')

// 带默认值工厂函数
const config = inject('config', () => ({ size: 'small' }), true)

// 使用 Symbol key
const ThemeKey = Symbol()
const theme = inject(ThemeKey)
</script>

TypeScript 类型支持:

// types.ts
import type { InjectionKey, Ref } from 'vue'

export interface Theme {
  color: string
  mode: 'light' | 'dark'
}

export const ThemeKey: InjectionKey<Ref<Theme>> = Symbol()
<!-- 祖先组件 -->
<script setup lang="ts">
import { provide, ref } from 'vue'
import { ThemeKey } from './types'

const theme = ref<Theme>({ color: '#fff', mode: 'light' })
provide(ThemeKey, theme)
</script>
<!-- 后代组件 -->
<script setup lang="ts">
import { inject } from 'vue'
import { ThemeKey } from './types'

const theme = inject(ThemeKey)  // 自动推导类型
</script>

对比表格:

特性 Vue2 Vue3
响应式 需手动处理 配合 ref/reactive 天然响应式
只读保护 readonly()
TypeScript 弱类型 InjectionKey 强类型
默认值 不支持 支持
API 风格 Options API Composition API

11. $root(访问根实例)

// Vue2
this.$root.globalMethod()
this.$root.globalData

// Vue3(仍可用但不推荐)
// 推荐用 provide/inject 或 Pinia

四、全局通信(2种方式)

12. Vuex(Vue2 主流)

// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count: 0,
    user: null
  },
  
  getters: {
    doubleCount: state => state.count * 2,
    isLogin: state => !!state.user
  },
  
  mutations: {
    INCREMENT(state, payload) {
      state.count += payload
    },
    SET_USER(state, user) {
      state.user = user
    }
  },
  
  actions: {
    async login({ commit }, credentials) {
      const user = await api.login(credentials)
      commit('SET_USER', user)
    },
    increment({ commit }, amount) {
      commit('INCREMENT', amount)
    }
  },
  
  modules: {
    cart: {
      namespaced: true,
      state: () => ({ items: [] }),
      mutations: { ... },
      actions: { ... }
    }
  }
})
// 组件中使用
export default {
  computed: {
    ...mapState(['count', 'user']),
    ...mapGetters(['doubleCount', 'isLogin']),
    ...mapState('cart', ['items'])
  },
  methods: {
    ...mapMutations(['INCREMENT']),
    ...mapActions(['login']),
    ...mapActions('cart', ['addItem']),
    
    handleClick() {
      this.$store.commit('INCREMENT', 1)
      this.$store.dispatch('login', { username, password })
      this.$store.state.count
      this.$store.getters.doubleCount
    }
  }
}

13. Pinia(Vue3 官方推荐)

// stores/counter.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

// 方式1:Options Store(类似 Vuex)
export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
    user: null
  }),
  
  getters: {
    doubleCount: (state) => state.count * 2,
    isLogin: (state) => !!state.user
  },
  
  actions: {
    increment(amount) {
      this.count += amount  // 直接修改,无需 mutation
    },
    async login(credentials) {
      this.user = await api.login(credentials)
    }
  }
})

// 方式2:Setup Store(推荐,更灵活)
export const useCounterStore = defineStore('counter', () => {
  // state
  const count = ref(0)
  const user = ref(null)
  
  // getters
  const doubleCount = computed(() => count.value * 2)
  const isLogin = computed(() => !!user.value)
  
  // actions
  function increment(amount) {
    count.value += amount
  }
  
  async function login(credentials) {
    user.value = await api.login(credentials)
  }
  
  return {
    count,
    user,
    doubleCount,
    isLogin,
    increment,
    login
  }
})
<!-- 组件中使用 -->
<script setup>
import { useCounterStore } from '@/stores/counter'
import { storeToRefs } from 'pinia'

const counter = useCounterStore()

// ❌ 错误:解构会失去响应式
const { count, doubleCount } = counter

// ✅ 正确:使用 storeToRefs
const { count, doubleCount } = storeToRefs(counter)

// actions 可以直接解构
const { increment, login } = counter

// 使用
counter.count++
counter.increment(5)
counter.$patch({ count: 10 })
counter.$patch((state) => {
  state.count++
  state.user = null
})
counter.$reset()  // 重置到初始状态
</script>

Vuex vs Pinia 对比:

特性 Vuex Pinia
Mutations 必须 ❌ 移除
Actions 异步操作 同步/异步都可
模块化 modules 嵌套 多个独立 store
TypeScript 需手动配置 原生支持
DevTools 支持 支持
体积 ~2.5KB ~1KB
学习曲线 较陡 平缓
代码量 较多 更少

五、特殊场景通信(3种方式)

14. Slot(插槽通信)

默认插槽

<!-- 父组件 -->
<template>
  <Card>
    <p>这是插槽内容</p>
  </Card>
</template>

<!-- 子组件 -->
<template>
  <div class="card">
    <slot></slot>  <!-- 渲染父组件传入的内容 -->
  </div>
</template>

具名插槽

<!-- 父组件 -->
<template>
  <Layout>
    <template #header>
      <h1>标题</h1>
    </template>
    
    <template #default>
      <p>主要内容</p>
    </template>
    
    <template #footer>
      <p>页脚</p>
    </template>
  </Layout>
</template>

<!-- 子组件 -->
<template>
  <div class="layout">
    <header>
      <slot name="header"></slot>
    </header>
    <main>
      <slot></slot>  <!-- 默认插槽 -->
    </main>
    <footer>
      <slot name="footer"></slot>
    </footer>
  </div>
</template>

作用域插槽(子传父数据)

<!-- 子组件 -->
<template>
  <ul>
    <li v-for="item in items" :key="item.id">
      <slot :item="item" :index="index"></slot>
    </li>
  </ul>
</template>

<script setup>
defineProps(['items'])
</script>
<!-- 父组件 Vue2 -->
<template>
  <List :items="users">
    <template slot-scope="{ item, index }">
      {{ index }}: {{ item.name }}
    </template>
  </List>
</template>
<!-- 父组件 Vue3 -->
<template>
  <List :items="users">
    <template #default="{ item, index }">
      {{ index }}: {{ item.name }}
    </template>
  </List>
</template>

Vue2 vs Vue3 插槽语法:

特性 Vue2 Vue3
具名插槽 slot="header" #header 或 v-slot:header
作用域插槽 slot-scope="props" #default="props"
废弃语法 slot-scope ❌ 移除

15. Teleport(Vue3 新增)

<!-- 将内容传送到 DOM 的其他位置 -->
<template>
  <button @click="open = true">打开弹窗</button>
  
  <Teleport to="body">
    <div v-if="open" class="modal">
      <p>我被渲染到 body 下</p>
      <button @click="open = false">关闭</button>
    </div>
  </Teleport>
</template>

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

应用场景:

  • 模态框
  • 通知提示
  • 全屏遮罩
  • 避免 z-index 和 overflow 问题

16. 动态组件通信

<template>
  <component 
    :is="currentComponent" 
    v-bind="componentProps"
    @custom-event="handleEvent"
  />
</template>

<script setup>
import { ref, computed } from 'vue'
import ComponentA from './ComponentA.vue'
import ComponentB from './ComponentB.vue'

const currentTab = ref('a')

const currentComponent = computed(() => {
  return currentTab.value === 'a' ? ComponentA : ComponentB
})

const componentProps = computed(() => {
  return currentTab.value === 'a' 
    ? { propA: 'valueA' }
    : { propB: 'valueB' }
})
</script>

六、完整对比总结表

通信方式 适用场景 Vue2 Vue3 主要差异
Props 父→子 props: {} defineProps() Vue3 支持 TS 泛型
Emit 子→父 this.$emit() defineEmits() Vue3 强制声明
v-model 双向绑定 value+input modelValue+update:modelValue Vue3 支持多个 v-model
.sync 双向绑定 ❌ 移除 用 v-model:xxx 替代
$refs 获取实例 全暴露 需 defineExpose Vue

七、面试怎么回答才精彩

🎯 初级回答(60分)

"Vue 组件通信主要有 props、emit、vuex 这几种方式。父子组件用 props 传数据,子组件用 emit 触发事件,全局状态用 vuex。"

问题: 太笼统,没有深度。


🎯 中级回答(80分)

"Vue 组件通信我会按场景分类:

  1. 父子通信:props down, events up,Vue3 支持多个 v-model
  2. 跨层级:provide/inject,Vue3 中支持响应式
  3. 兄弟组件:通过父组件中转或使用事件总线
  4. 全局状态:Vuex 或 Pinia,Pinia 是 Vue3 官方推荐

还有 refs、refs、attrs 等辅助方式,但要注意 Vue3 移除了 $children。"

优点: 有分类、有对比。


🎯 高级回答(95分)✨

"我会从设计原则实际场景两个维度来回答:

一、设计原则

  • 单向数据流:优先使用 props/emit,保持数据流向清晰
  • 最小知道原则:组件只知道必要的信息,避免 parent/parent/children
  • 关注点分离:UI 状态用组件通信,业务状态用状态管理

二、场景选择

  1. 父子组件(1-2层) :props/emit + v-model
    • Vue3 的 defineProps 有更好的类型推导
    • 多个 v-model 解决了 Vue2 的 .sync 问题
  2. 跨层级(3层+) :provide/inject
    • Vue2 需要手动处理响应式(用函数返回)
    • Vue3 原生支持响应式,配合 readonly 防止子组件修改
  3. 兄弟/无关组件
    • 简单场景:父组件中转
    • 复杂场景:Pinia(Vue3)或 Vuex(Vue2)
    • 避免 Event Bus,容易造成内存泄漏和难以追踪的 bug
  4. 全局状态:Pinia > Vuex
    • Pinia 去掉了 mutations,代码更简洁
    • 天然支持 TypeScript
    • 自动代码分割,性能更好

三、实战经验

  • 曾经遇到过 Event Bus 导致的内存泄漏,组件销毁时忘记 $off
  • 深层 props 传递用 provide/inject 重构后,代码可维护性提升 40%
  • 迁移到 Pinia 后,状态管理代码量减少 30%,类型安全性大幅提升

四、Vue3 特别注意

  • $children 被移除,需要用 ref + defineExpose
  • $listeners 合并到 $attrs
  • Composition API 中用 useAttrs()useSlots() 访问

五、性能优化

  • 大数据量用 provide/inject 比 props 逐层传递性能更好
  • 频繁变化的数据避免放 Vuex/Pinia,会触发所有订阅者更新
  • 使用 shallowRefshallowReactive 优化大对象通信"

🔥 加分项

如果面试官追问,可以补充:

  1. 源码层面

    • "Props 的校验是在 initProps 阶段完成的"
    • "provide/inject 通过原型链查找实现"
  2. TypeScript 支持

// Vue3 + TS 最佳实践
interface Props {
  msg: string
  count?: number
}

const props = defineProps<Props>()
const emit = defineEmits<{
  (e: 'update', value: string): void
}>()
  1. 性能对比

    • "测试过 10 层组件,provide/inject 比 props 传递快 15%"
    • "Pinia 的 devtools 性能比 Vuex 好 2 倍"
  2. 架构设计

    • "我们项目用 Pinia 管理全局状态,provide/inject 处理主题、权限等配置,props/emit 处理业务逻辑"

💡 面试官可能的追问

Q1: "为什么不推荐 Event Bus?"

"三个原因:1) 事件难以追踪,调试困难;2) 容易忘记销毁导致内存泄漏;3) 命名冲突风险。实际项目中用 Pinia 替代更好。"

Q2: "provide/inject 如何保证响应式?"

"Vue2 需要 provide 返回函数或用 Vue.observable;Vue3 直接传 ref/reactive 即可,建议用 readonly 包裹防止子组件修改。"

Q3: "大型项目如何组织组件通信?"

"分层设计:UI 层用 props/emit,业务层用 Pinia modules,配置层用 provide/inject,避免混用导致数据流混乱。"

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐