在 React 中,Function Component 没有传统的生命周期方法,而是通过 Hooks 模拟和替代了 Class 组件的生命周期逻辑。Function Component 的“生命周期”可以理解为 副作用(Effect)的触发时机 和 状态(State)的更新过程。
一、Function Component 的“生命周期”阶段
可以将 Function Component 的执行过程划分为以下阶段:
| 阶段 | 对应 Class 生命周期 | 核心 Hooks 与行为 |
|---|---|---|
| 挂载阶段 | constructor + componentDidMount | useState 初始化 → 执行函数体 → 同步执行 useLayoutEffect → 异步执行 useEffect |
| 更新阶段 | componentDidUpdate | 状态/Props 变化 → 重新执行函数体 → 同步执行 useLayoutEffect → 异步执行 useEffect |
| 卸载阶段 | componentWillUnmount | 组件卸载前执行 useEffect 的清理函数 |
二、Hooks 执行顺序与核心原理
以下是 Function Component 中 Hooks 与副作用的详细执行流程:
1. 挂载阶段(Mounting)
function MyComponent() {
// 1. 初始化状态(useState)
const [count, setCount] = useState(0);
// 2. 同步副作用(useLayoutEffect)
useLayoutEffect(() => {
// DOM 更新后同步执行(类似 componentDidMount + componentDidUpdate)
console.log('useLayoutEffect (mount)');
return () => {
// 清理函数(类似 componentWillUnmount)
console.log('useLayoutEffect cleanup (mount)');
};
}, []);
// 3. 异步副作用(useEffect)
useEffect(() => {
// DOM 更新后异步执行(类似 componentDidMount)
console.log('useEffect (mount)');
return () => {
// 清理函数(类似 componentWillUnmount)
console.log('useEffect cleanup (mount)');
};
}, []);
// 4. 函数体执行(生成 JSX)
console.log('render');
return <div>{count}</div>;
}
执行顺序:
useState初始化状态。- 执行函数体逻辑(生成 JSX)。
- 同步执行
useLayoutEffect(在浏览器绘制 DOM 前执行)。 - 异步执行
useEffect(在浏览器绘制 DOM 后执行)。
2. 更新阶段(Updating)
当状态或 Props 变化时:
function MyComponent({ propValue }) {
const [count, setCount] = useState(0);
useLayoutEffect(() => {
console.log('useLayoutEffect (update)');
return () => {
// 先执行清理函数(类似 componentWillUpdate)
console.log('useLayoutEffect cleanup (update)');
};
}, [count]); // 依赖项变化时触发
useEffect(() => {
console.log('useEffect (update)');
return () => {
console.log('useEffect cleanup (update)');
};
}, [count]);
console.log('render');
return <div onClick={() => setCount(count + 1)}>{count}</div>;
}
执行顺序:
- 函数体重新执行(生成新的 JSX)。
- 同步执行
useLayoutEffect的清理函数(上一次的清理逻辑)。 - 同步执行新的
useLayoutEffect。 - 异步执行上一次
useEffect的清理函数。 - 异步执行新的
useEffect。
3. 卸载阶段(Unmounting)
当组件从 DOM 中移除时:
function ParentComponent() {
const [showChild, setShowChild] = useState(true);
return (
<div>
<button onClick={() => setShowChild(false)}>Unmount Child</button>
{showChild && <MyComponent />}
</div>
);
}
执行顺序:
- 同步执行
useLayoutEffect的清理函数。 - 异步执行
useEffect的清理函数。
三、关键 Hook 的“生命周期”行为
1. useEffect
- 触发时机:DOM 更新后异步执行。
- 清理函数:在下一次
useEffect执行前或组件卸载时执行。 - 依赖项控制:通过第二个参数(依赖数组)决定何时重新执行:
- 空数组
[]:仅在挂载时执行(类似componentDidMount)。 - 有依赖项
[a, b]:依赖变化时执行(类似componentDidUpdate)。 - 无依赖项:每次渲染后都执行(谨慎使用)。
- 空数组
2. useLayoutEffect
- 触发时机:DOM 更新后同步执行(在浏览器绘制前)。
- 典型场景:读取 DOM 布局(如元素尺寸、滚动位置)并同步更新。
- 注意事项:避免阻塞浏览器绘制,优先使用
useEffect。
3. useState / useReducer
- 状态更新:状态变化会触发函数组件的重新执行(类似
render)。 - 批量更新:React 会合并多个
setState调用,优化渲染性能。
4. useMemo / useCallback
- 优化渲染:缓存计算结果或函数引用,避免无效重渲染(类似
shouldComponentUpdate)。 - 依赖项控制:依赖变化时才重新计算值或生成新函数。
四、Function Component 生命周期流程图
挂载阶段:
函数体执行 → useLayoutEffect → useEffect
更新阶段:
函数体执行 → useLayoutEffect清理 → useLayoutEffect → useEffect清理 → useEffect
卸载阶段:
useLayoutEffect清理 → useEffect清理
五、与 Class 组件生命周期的对比
| Class 生命周期方法 | Function Component 等效实现 |
|---|---|
constructor | useState 初始化 |
componentDidMount | useEffect(fn, []) |
componentDidUpdate | useEffect(fn, [deps]) |
componentWillUnmount | useEffect(() => { return fn; }, []) |
shouldComponentUpdate | React.memo 或 useMemo/useCallback 优化 |
getSnapshotBeforeUpdate | useLayoutEffect + DOM 操作 |
六、常见问题与最佳实践
-
无限循环问题:
- 错误示例:在
useEffect中修改依赖项状态且未正确设置依赖。
useEffect(() => { setCount(count + 1); // 依赖 count 但未声明,导致无限循环 }, []); // ❌ 错误:依赖项缺失- 修复方法:明确依赖项或使用函数式更新。
useEffect(() => { setCount(c => c + 1); // ✅ 函数式更新,无需依赖 count }, []); - 错误示例:在
-
内存泄漏问题:
- 错误示例:未清理定时器或订阅。
useEffect(() => { const timer = setInterval(() => {}, 1000); // ❌ 错误:未返回清理函数 }, []);- 修复方法:始终返回清理函数。
useEffect(() => { const timer = setInterval(() => {}, 1000); return () => clearInterval(timer); // ✅ 清理定时器 }, []); -
性能优化:
- 使用
React.memo包裹组件,避免父组件重渲染导致的子组件无效更新。 - 使用
useMemo缓存复杂计算结果,使用useCallback缓存函数引用。
- 使用
七、总结
Function Component 的“生命周期”本质上是 副作用和状态变化的时序控制:
- 挂载阶段:初始化状态 → 执行函数体 → 同步副作用 → 异步副作用。
- 更新阶段:重新执行函数体 → 清理旧副作用 → 执行新副作用。
- 卸载阶段:清理所有副作用。
通过合理使用 useEffect、useLayoutEffect 和优化 Hooks(如 useMemo、useCallback),可以精准控制组件行为,实现高性能、可维护的 React 应用。