组件生命周期 #
一、生命周期概述 #
组件生命周期是指组件从创建到销毁的整个过程。理解生命周期对于正确处理副作用至关重要。
1.1 生命周期阶段 #
text
┌─────────────────────────────────────────────────────┐
│ 组件生命周期 │
├─────────────────────────────────────────────────────┤
│ │
│ 挂载阶段(Mounting) │
│ ├── constructor │
│ ├── getDerivedStateFromProps │
│ ├── render │
│ └── componentDidMount │
│ │
│ 更新阶段(Updating) │
│ ├── getDerivedStateFromProps │
│ ├── shouldComponentUpdate │
│ ├── render │
│ ├── getSnapshotBeforeUpdate │
│ └── componentDidUpdate │
│ │
│ 卸载阶段(Unmounting) │
│ └── componentWillUnmount │
│ │
│ 错误处理(Error Handling) │
│ ├── getDerivedStateFromError │
│ └── componentDidCatch │
│ │
└─────────────────────────────────────────────────────┘
二、类组件生命周期 #
2.1 挂载阶段 #
javascript
import { Component } from 'react';
class MountingDemo extends Component {
constructor(props) {
super(props);
console.log('1. constructor');
this.state = { count: 0 };
}
static getDerivedStateFromProps(props, state) {
console.log('2. getDerivedStateFromProps');
return null;
}
render() {
console.log('3. render');
return <div>{this.state.count}</div>;
}
componentDidMount() {
console.log('4. componentDidMount');
// 适合进行:API请求、订阅事件、DOM操作
}
}
执行顺序:
text
constructor → getDerivedStateFromProps → render → componentDidMount
2.2 更新阶段 #
javascript
class UpdatingDemo extends Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
static getDerivedStateFromProps(props, state) {
console.log('1. getDerivedStateFromProps');
return null;
}
shouldComponentUpdate(nextProps, nextState) {
console.log('2. shouldComponentUpdate');
return true;
}
render() {
console.log('3. render');
return (
<div>
<p>{this.state.count}</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
增加
</button>
</div>
);
}
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log('4. getSnapshotBeforeUpdate');
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('5. componentDidUpdate');
}
}
触发更新的情况:
- 调用
setState() - 父组件传递新的 Props
- 调用
forceUpdate()
2.3 卸载阶段 #
javascript
class UnmountingDemo extends Component {
componentDidMount() {
this.timer = setInterval(() => {
console.log('tick');
}, 1000);
}
componentWillUnmount() {
console.log('componentWillUnmount');
clearInterval(this.timer);
}
render() {
return <div>Unmounting Demo</div>;
}
}
2.4 错误处理 #
javascript
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error('Error:', error);
console.error('Error Info:', errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>出错了!</h1>;
}
return this.props.children;
}
}
三、函数组件生命周期 #
3.1 useEffect基础 #
函数组件使用 useEffect 处理副作用:
javascript
import { useState, useEffect } from 'react';
function LifecycleDemo() {
const [count, setCount] = useState(0);
// 相当于 componentDidMount
useEffect(() => {
console.log('组件挂载');
}, []);
// 相当于 componentDidMount + componentDidUpdate
useEffect(() => {
console.log('count变化:', count);
}, [count]);
// 相当于 componentWillUnmount
useEffect(() => {
return () => {
console.log('组件卸载');
};
}, []);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
</div>
);
}
3.2 useEffect完整形式 #
javascript
useEffect(() => {
// 副作用逻辑
console.log('副作用执行');
// 清理函数(可选)
return () => {
console.log('清理函数执行');
};
}, [dependencies]);
依赖数组说明:
| 依赖数组 | 执行时机 |
|---|---|
| 无 | 每次渲染后都执行 |
[] |
仅挂载时执行一次 |
[a, b] |
a 或 b 变化时执行 |
3.3 类组件 vs 函数组件对照 #
javascript
// 类组件
class Example extends Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
componentDidMount() {
document.title = `Count: ${this.state.count}`;
}
componentDidUpdate() {
document.title = `Count: ${this.state.count}`;
}
componentWillUnmount() {
console.log('清理');
}
render() {
return (
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
{this.state.count}
</button>
);
}
}
// 函数组件
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `Count: ${count}`;
return () => {
console.log('清理');
};
}, [count]);
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
}
四、常见生命周期场景 #
4.1 数据请求 #
javascript
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let isMounted = true;
const fetchUser = async () => {
try {
setLoading(true);
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
if (isMounted) {
setUser(data);
setError(null);
}
} catch (err) {
if (isMounted) {
setError(err.message);
}
} finally {
if (isMounted) {
setLoading(false);
}
}
};
fetchUser();
return () => {
isMounted = false;
};
}, [userId]);
if (loading) return <div>加载中...</div>;
if (error) return <div>错误: {error}</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
4.2 事件订阅 #
javascript
function WindowSize() {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
const handleResize = () => {
setSize({
width: window.innerWidth,
height: window.innerHeight
});
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return (
<div>
{size.width} x {size.height}
</div>
);
}
4.3 定时器 #
javascript
function Timer() {
const [seconds, setSeconds] = useState(0);
const [isRunning, setIsRunning] = useState(false);
useEffect(() => {
if (!isRunning) return;
const timer = setInterval(() => {
setSeconds(prev => prev + 1);
}, 1000);
return () => {
clearInterval(timer);
};
}, [isRunning]);
return (
<div>
<p>{seconds}秒</p>
<button onClick={() => setIsRunning(!isRunning)}>
{isRunning ? '暂停' : '开始'}
</button>
<button onClick={() => setSeconds(0)}>重置</button>
</div>
);
}
4.4 DOM操作 #
javascript
function FocusInput() {
const inputRef = useRef();
useEffect(() => {
inputRef.current.focus();
}, []);
return <input ref={inputRef} />;
}
五、useLayoutEffect #
5.1 与useEffect的区别 #
javascript
// useEffect - 异步执行,不阻塞浏览器绘制
useEffect(() => {
console.log('useEffect');
});
// useLayoutEffect - 同步执行,阻塞浏览器绘制
useLayoutEffect(() => {
console.log('useLayoutEffect');
});
执行顺序:
text
render → useLayoutEffect → 浏览器绘制 → useEffect
5.2 使用场景 #
javascript
function Tooltip({ children, targetRect }) {
const tooltipRef = useRef();
const [tooltipHeight, setTooltipHeight] = useState(0);
useLayoutEffect(() => {
const { height } = tooltipRef.current.getBoundingClientRect();
setTooltipHeight(height);
}, [children]);
let tooltipX = 0;
let tooltipY = 0;
if (targetRect) {
tooltipX = targetRect.left;
tooltipY = targetRect.top - tooltipHeight;
if (tooltipY < 0) {
tooltipY = targetRect.bottom;
}
}
return (
<div
ref={tooltipRef}
style={{
position: 'absolute',
left: tooltipX,
top: tooltipY
}}
>
{children}
</div>
);
}
六、生命周期最佳实践 #
6.1 避免在渲染中产生副作用 #
javascript
// ❌ 错误:在渲染中产生副作用
function Counter() {
const [count, setCount] = useState(0);
document.title = `Count: ${count}`; // 副作用
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
// ✅ 正确:使用useEffect
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
6.2 正确设置依赖 #
javascript
// ❌ 缺少依赖
useEffect(() => {
fetchData(userId);
}, []); // userId变化时不会重新请求
// ✅ 包含所有依赖
useEffect(() => {
fetchData(userId);
}, [userId]);
6.3 清理副作用 #
javascript
// ❌ 没有清理
useEffect(() => {
const timer = setInterval(() => {
console.log('tick');
}, 1000);
}, []);
// ✅ 正确清理
useEffect(() => {
const timer = setInterval(() => {
console.log('tick');
}, 1000);
return () => {
clearInterval(timer);
};
}, []);
6.4 避免无限循环 #
javascript
// ❌ 无限循环
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1); // 每次渲染都更新状态
}, [count]);
return <div>{count}</div>;
}
// ✅ 避免循环
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
if (count < 10) {
setCount(count + 1);
}
}, [count]);
return <div>{count}</div>;
}
七、生命周期方法速查表 #
7.1 类组件 #
| 方法 | 时机 | 用途 |
|---|---|---|
| constructor | 创建组件时 | 初始化state |
| getDerivedStateFromProps | 渲染前 | 根据props更新state |
| render | 渲染时 | 返回JSX |
| componentDidMount | 挂载后 | API请求、订阅 |
| shouldComponentUpdate | 更新前 | 性能优化 |
| getSnapshotBeforeUpdate | DOM更新前 | 获取DOM信息 |
| componentDidUpdate | 更新后 | DOM操作 |
| componentWillUnmount | 卸载前 | 清理工作 |
7.2 函数组件 #
| Hook | 用途 |
|---|---|
| useEffect | 副作用处理 |
| useLayoutEffect | 同步DOM操作 |
| useInsertionEffect | CSS-in-JS注入 |
八、总结 #
| 要点 | 说明 |
|---|---|
| 理解阶段 | 挂载、更新、卸载 |
| 正确使用 | useEffect处理副作用 |
| 清理资源 | 返回清理函数 |
| 依赖数组 | 正确设置依赖项 |
核心原则:
- 副作用放在 useEffect 中
- 记得清理订阅和定时器
- 正确设置依赖数组
- 使用 useLayoutEffect 处理 DOM 测量
最后更新:2026-03-26