Vue3 高频题 · 第 5 题
主问题
问:Vue3 的父子组件通信方式有哪些?分别适用于什么场景?
一、面试官期望你说出的目录(总览)
Vue3 父子通信主要包括:
父 → 子
- props(最常用)
- defineProps(script setup)
- v-model(子组件双向绑定)
- provide / inject(跨层级)
子 → 父
- emit(自定义事件)
双向 & 深层 / 全局
- v-model 修饰事件 + 多 v-model
- provide / inject(跨层级)
- 全局 store(Pinia)
- 模板引用 ref + defineExpose(父直接调用子方法)
二、核心回答(逐一解释 + 场景)
1)props:父 → 子(最标准方式)
父组件传值给子组件。
xml
<!-- parent -->
<Child :title="msg" />
<!-- child -->
<script setup>
const props = defineProps({
title: String
})
</script>
适用场景
- 结构简单
- 仅需单向数据流
2)emit:子 → 父
子组件触发事件,让父组件接收。
xml
<!-- child -->
<script setup>
const emit = defineEmits(['update'])
emit('update', 123)
</script>
<!-- parent -->
<Child @update="getVal" />
适用场景
- 子组件需要通知父组件:点击、选择、提交等。
3)v-model:父 ↔ 子 双向绑定
Vue3 扩展了 v-model,可以多字段。
xml
<!-- parent -->
<Child v-model="username" />
<!-- child -->
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>
适用场景
- 输入类组件(input/select)
- 封装表单组件
4)多 v-model
xml
<!-- parent -->
<Child v-model:first-name="first" v-model:last-name="last" />
<!-- child -->
defineProps(['firstName', 'lastName'])
defineEmits(['update:firstName', 'update:lastName'])
适合场景
- 自定义表单组件,同时绑定多个字段
5)provide / inject(跨层级通信)
不需要一层层 props 传递。
arduino
// parent
provide('theme', 'dark')
// child descendant
const theme = inject('theme')
适合场景
- 组件跨层级传递全局信息:主题、配置、上下文数据
- 常用于 UI 组件库(如 Element-Plus)
6)父 ref 调用子组件方法(defineExpose)
父组件直接访问子组件实例内部方法。
xml
<!-- child -->
<script setup>
function validate() { ... }
defineExpose({ validate })
</script>
xml
<!-- parent -->
<Child ref="childRef" />
childRef.value.validate()
适用场景
- 表单校验
- 滚动控制
- 模态框的 open/close
7)使用 Pinia / Vuex(全局状态)
父子、跨组件、兄弟全都能用。
适合场景
- 全局共享状态:用户信息、设置、token
- 复杂项目
三、深度追问(面试官常用)
(1)props 为什么是单向数据流?能不能改?
不能! Vue 禁止子组件直接修改 props。
因为会导致状态来源混乱,难以维护。
如果需要修改:
- 用 emit 通知父组件
- 或者在子组件内部复制一份:
csharp
const localVal = ref(props.value)
(2)provide 的数据是响应式吗?
默认 不是响应式的。
需要响应式需要这样:
less
provide('user', reactive({...}))
provide('count', ref(0))
inject 自动保持响应式。
(3)为什么某些组件库大量使用 provide/inject?
因为组件层级深(例如 Menu / Form / Table)
props 传递太麻烦
provide/inject 结构更清晰
(4)父组件 ref 调用子方法有什么风险?
- 父对子产生强耦合
- 组件难复用
- 必须等待子组件挂载后才能调用
四、面试官 killer 问题(加分项)
❓ "父组件能否监听子组件 props 的变化?"
答:不能直接监听,但可以:
- 使用 watch(() => props.xxx) 在子组件监听 props
- 或者在父组件监听自己的数据(推荐)
❓ "如果 provide 的数据是 reactive,对象属性新增会响应吗?"
会。因为 Proxy 拦截的是整个对象。
❓ "v-model 和 props + emit 比,有什么优点?"
- 更统一的 API
- 多字段 v-model
- 更抽象,更适合封装组件
五、最终小结(可以背诵)
父传子用 props ,子传父用 emit 。
双向绑定用 v-model 。
跨层级用 provide/inject 。
父调子方法用 ref + defineExpose 。
复杂共享状态用 Pinia。