在现代前端框架(如 Vue 和 React)的面试中,"父子组件如何通信?生命周期的执行顺序是怎样的? " 是一道经典且高频的综合题。它不仅考察你对框架 API 的掌握,更深入检验你对组件化思想、数据流、渲染机制和副作用处理的理解。
本文将以 Vue 3 和 React 为例,从基础到原理,全面剖析父子组件通信方式与生命周期执行顺序,助你在面试中脱颖而出。
一、父子组件通信的五大方式
组件通信的核心是数据流的传递与同步 。在单向数据流(Unidirectional Data Flow)理念下,父组件通过 props 向下传递数据 ,子组件通过 事件(Event)向上通信。
1. Props Down:父 → 子(数据传递)
Vue 3 示例
vue
<!-- Parent.vue -->
<template>
<Child :msg="message" :user="userInfo" />
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const message = ref('Hello from Parent')
const userInfo = { name: 'Alice', age: 25 }
</script>
<!-- Child.vue -->
<script setup>
// 接收 props
const props = defineProps({
msg: String,
user: Object
})
console.log(props.msg) // 'Hello from Parent'
</script>
React 示例
jsx
// Parent.js
function Parent() {
const [message, setMessage] = useState('Hello from Parent');
const userInfo = { name: 'Alice', age: 25 };
return <Child msg={message} user={userInfo} />;
}
// Child.js
function Child({ msg, user }) {
console.log(msg); // 'Hello from Parent'
return <div>{msg}</div>;
}
✅ 关键点:
- Props 是只读的,子组件不应直接修改。
- 传递引用类型(对象、数组)时,子组件修改其内部属性会影响父组件(浅共享),需避免。
2. Events Up:子 → 父(事件通知)
Vue 3:emit
事件
vue
<!-- Child.vue -->
<script setup>
const emit = defineEmits(['update', 'close'])
function handleClick() {
emit('update', 'New Value')
emit('close')
}
</script>
<!-- Parent.vue -->
<template>
<Child @update="handleUpdate" @close=" handleClose" />
</template>
<script setup>
function handleUpdate(value) {
console.log('Received:', value)
}
function handleClose() {
console.log('Child closed')
}
</script>
React:回调函数(Callback)
jsx
// Parent.js
function Parent() {
const handleUpdate = (value) => {
console.log('Received:', value);
};
const handleClose = () => {
console.log('Child closed');
};
return <Child onUpdate={handleUpdate} onClose={handleClose} />;
}
// Child.js
function Child({ onUpdate, onClose }) {
return (
<button onClick={() => {
onUpdate('New Value');
onClose();
}}>
Click
</button>
);
}
✅ 关键点:这是最标准、最安全的子 → 父通信方式。
3. v-model / v-model:value(双向绑定)
Vue 3 的语法糖,本质是 :modelValue
+ @update:modelValue
。
vue
<!-- Parent.vue -->
<Child v-model="message" />
<!-- 等价于 -->
<Child
:modelValue="message"
@update:modelValue="value => message = value"
/>
<!-- Child.vue -->
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
function handleChange(e) {
emit('update:modelValue', e.target.value)
}
</script>
✅ 适用场景 :表单组件(如
Input
,Select
)。
4. $refs / ref(直接访问子组件实例)
Vue 3
vue
<!-- Parent.vue -->
<template>
<Child ref="childRef" />
</template>
<script setup>
import { ref, onMounted } from 'vue'
import Child from './Child.vue'
const childRef = ref(null)
onMounted(() => {
childRef.value.someMethod() // 调用子组件方法
})
</script>
<!-- Child.vue -->
<script setup>
import { defineExpose } from 'vue'
function someMethod() {
console.log('Called from parent')
}
// 暴露给父组件
defineExpose({ someMethod })
</script>
React:useRef
+ forwardRef
jsx
// Child.js
const Child = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
someMethod() {
console.log('Called from parent');
}
}));
return <div>Child</div>;
});
// Parent.js
function Parent() {
const childRef = useRef();
useEffect(() => {
childRef.current.someMethod();
}, []);
return <Child ref={childRef} />;
}
⚠️ 注意 :应尽量避免使用
ref
,破坏了组件的封装性,仅用于 DOM 操作或特定方法调用。
5. Provide / Inject(跨层级通信)
适用于祖孙组件通信,避免"props 逐层透传"。
vue
<!-- App.vue (祖先) -->
<script setup>
import { provide } from 'vue'
provide('theme', 'dark')
provide('user', { name: 'Admin' })
</script>
<!-- AnyChild.vue (任意后代) -->
<script setup>
import { inject } from 'vue'
const theme = inject('theme', 'light') // 第二个参数是默认值
const user = inject('user')
</script>
✅ 优点 :解耦层级依赖。 ❌ 缺点:数据流不清晰,调试困难。
二、父子组件生命周期执行顺序深度解析
理解生命周期顺序,是避免"子组件未挂载就访问 "、"卸载时内存泄漏"等 bug 的关键。
Vue 3 执行顺序(Composition API)
1. 首次挂载(Mount)
js
// Parent
setup() // 1
onBeforeMount() // 3
onMounted() // 5
// Child
setup() // 2
onBeforeMount() // 4
onMounted() // 6
🔍 流程:
- 父组件
setup
执行(准备数据和逻辑)。- 子组件
setup
执行。- 父组件进入
onBeforeMount
(DOM 未生成)。- 子组件进入
onBeforeMount
。- 子组件
onMounted
触发(子组件 DOM 已挂载)。- 父组件
onMounted
触发(父组件等待所有子组件挂载完成才算自己挂载完成)。
✅ 结论 :onMounted
的完成顺序是 子 → 父。
2. 更新(Update)
js
// Parent
onBeforeUpdate() // 1
onUpdated() // 3
// Child
onBeforeUpdate() // 2
onUpdated() // 3
🔍 流程:
- 父组件触发
onBeforeUpdate
。- 子组件触发
onBeforeUpdate
。- 子组件
onUpdated
。- 父组件
onUpdated
。
✅ 结论 :更新也是 子先完成,父后完成。
3. 卸载(Unmount)
js
// Parent
onBeforeUnmount() // 1
onUnmounted() // 3
// Child
onBeforeUnmount() // 2
onUnmounted() // 3
✅ 结论 :卸载顺序 子 → 父 完成。
React 执行顺序(函数组件 + useEffect)
1. 首次渲染
js
// Parent
render() // 1
useEffect(() => { /* mount */ }) // 3
// Child
render() // 2
useEffect(() => { /* mount */ }) // 4
🔍 流程:
- 父组件
render
(生成虚拟 DOM)。- 子组件
render
。- DOM 提交到页面。
useEffect
异步执行:父 → 子。
✅ 结论 :useEffect
执行顺序是 父 → 子。
2. 更新
顺序与首次渲染一致:父 render → 子 render → DOM 提交 → 父 useEffect → 子 useEffect。
3. 卸载
js
// cleanup 执行顺序
Parent cleanup // 1
Child cleanup // 2
✅ 结论 :
useEffect
的 cleanup 函数执行顺序是 父 → 子。
三、通信与生命周期的协同应用
场景:父组件等待子组件初始化完成
vue
<!-- Parent.vue -->
<script setup>
import { ref, onMounted } from 'vue'
import Child from './Child.vue'
const childRef = ref(null)
onMounted(() => {
// 此时 childRef.value 已存在,且子组件已挂载
childRef.value.initialize()
})
</script>
✅ 依据 :
onMounted
触发时,所有子组件已挂载完毕。
四、总结:一张表看懂核心要点
维度 | Vue 3 | React |
---|---|---|
父 → 子通信 | props |
props |
子 → 父通信 | emit 事件 |
回调函数(Callback) |
双向绑定 | v-model |
受控组件 + onChange |
直接访问 | ref + defineExpose |
useRef + forwardRef + useImperativeHandle |
跨层级通信 | provide / inject |
Context API |
挂载完成顺序 | 子 → 父 (onMounted ) |
父 → 子 (useEffect ) |
更新完成顺序 | 子 → 父 (onUpdated ) |
父 → 子 (useEffect ) |
卸载顺序 | 子 → 父 (onUnmounted ) |
父 → 子 (cleanup ) |
面试加分回答
"父子组件通信应遵循单向数据流 原则:父传
props
,子发event
。v-model
和ref
是语法糖和特殊手段,应谨慎使用。生命周期顺序的核心是渲染从父到子,完成从子到父 (Vue),而 React 的useEffect
是在渲染后统一执行。理解这一点,能帮助我们正确处理 DOM 操作、事件绑定、资源清理和异步初始化,避免因时机错误导致的 bug。"
掌握这些,你不仅能回答面试题,更能设计出健壮、可维护的组件体系。