在 React 中,父组件和子组件的 useEffect
及其清理函数(return
)的执行顺序遵循特定的规则,与组件的生命周期阶段(挂载、更新、卸载)密切相关。以下是详细说明:
核心概念澄清
useEffect
的return
:是清理函数,用于在组件卸载或依赖项变化时清除副作用(如取消订阅、清理定时器等)。与render
方法无关。render
:负责渲染 UI,是同步执行的。父组件的render
会触发子组件的render
。
执行顺序规则
1. 挂载阶段(Mounting)
- 父组件
render
→ 父组件 UI 渲染。 - 子组件
render
→ 子组件 UI 渲染。 - 子组件的
useEffect
回调 → 执行子组件的副作用。 - 父组件的
useEffect
回调 → 执行父组件的副作用。
控制台输出示例:
plaintext
复制
Child useEffect - Mounted
Parent useEffect - Mounted
2. 更新阶段(Updating)
- 父组件
render
→ 父组件重新渲染。 - 子组件
render
→ 子组件重新渲染。 - 父组件的清理函数(
useEffect
的return
) → 清理父组件的旧副作用。 - 子组件的清理函数(
useEffect
的return
) → 清理子组件的旧副作用。 - 子组件的
useEffect
回调 → 执行子组件的新副作用。 - 父组件的
useEffect
回调 → 执行父组件的新副作用。
控制台输出示例:
plaintext
复制
Parent useEffect - Cleanup (旧依赖)
Child useEffect - Cleanup (旧依赖)
Child useEffect - Updated (新依赖)
Parent useEffect - Updated (新依赖)
3. 卸载阶段(Unmounting)
- 子组件的清理函数(
useEffect
的return
) → 清理子组件的副作用。 - 父组件的清理函数(
useEffect
的return
) → 清理父组件的副作用。
控制台输出示例:
plaintext
复制
Child useEffect - Cleanup (卸载)
Parent useEffect - Cleanup (卸载)
为什么是这个顺序?
-
挂载阶段:
- React 按照组件树的层级渲染(父 → 子)。
- 但
useEffect
的回调是异步执行的,且按照子组件 → 父组件的顺序触发,确保子组件的副作用先于父组件完成。
-
更新阶段:
- 清理函数(
return
)的执行顺序与useEffect
回调相反(父组件 → 子组件),确保依赖项变化时,旧的副作用先被清理。 - 新的副作用回调仍按子组件 → 父组件顺序执行。
- 清理函数(
-
卸载阶段:
- 子组件作为父组件的一部分,需要先被销毁,因此清理函数按子组件 → 父组件执行。
示例代码验证
jsx
复制
import React, { useEffect, useState } from 'react';
function Parent() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('Parent useEffect - Mounted/Updated');
return () => console.log('Parent useEffect - Cleanup');
}, [count]);
return (
<div>
<button onClick={() => setCount(count + 1)}>Update Parent</button>
<Child />
</div>
);
}
function Child() {
useEffect(() => {
console.log('Child useEffect - Mounted/Updated');
return () => console.log('Child useEffect - Cleanup');
}, []);
return <div>Child Component</div>;
}
操作步骤及输出:
-
首次加载:
plaintext
复制
Child useEffect - Mounted/Updated Parent useEffect - Mounted/Updated
-
点击按钮更新父组件:
plaintext
复制
Parent useEffect - Cleanup // 父组件清理旧副作用 Child useEffect - Cleanup // 子组件清理旧副作用 Child useEffect - Mounted/Updated // 子组件执行新副作用 Parent useEffect - Mounted/Updated // 父组件执行新副作用
-
卸载父组件:
plaintext
复制
Child useEffect - Cleanup // 子组件清理 Parent useEffect - Cleanup // 父组件清理
总结
- 挂载阶段:子组件
useEffect
→ 父组件useEffect
。 - 更新阶段:父组件清理 → 子组件清理 → 子组件
useEffect
→ 父组件useEffect
。 - 卸载阶段:子组件清理 → 父组件清理。
理解这些顺序有助于避免副作用交叉污染,确保逻辑正确性和性能优化。