组件生命周期 #
一、生命周期概述 #
1.1 什么是生命周期 #
组件从创建到销毁的过程,分为三个阶段:
text
挂载 → 更新 → 卸载
1.2 生命周期图示 #
text
┌─────────────────────────────────────────────────┐
│ 挂载阶段 │
├─────────────────────────────────────────────────┤
│ constructor → getDerivedStateFromProps │
│ → render → componentDidMount │
└─────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ 更新阶段 │
├─────────────────────────────────────────────────┤
│ getDerivedStateFromProps → shouldComponentUpdate│
│ → render → getSnapshotBeforeUpdate │
│ → componentDidUpdate │
└─────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ 卸载阶段 │
├─────────────────────────────────────────────────┤
│ componentWillUnmount │
└─────────────────────────────────────────────────┘
二、函数组件生命周期 #
2.1 使用 useEffect #
jsx
import { useEffect, useState } from 'preact/hooks';
function Example() {
const [count, setCount] = useState(0);
// 挂载和更新时执行
useEffect(() => {
console.log('Component mounted or updated');
});
// 仅挂载时执行
useEffect(() => {
console.log('Component mounted');
}, []);
// count 变化时执行
useEffect(() => {
console.log('Count changed:', count);
}, [count]);
return <div>{count}</div>;
}
2.2 清理副作用 #
jsx
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds(s => s + 1);
}, 1000);
// 清理函数(卸载时执行)
return () => {
clearInterval(interval);
console.log('Timer cleaned up');
};
}, []);
return <div>Seconds: {seconds}</div>;
}
2.3 常见使用场景 #
jsx
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
// 数据获取
useEffect(() => {
let cancelled = false;
async function fetchUser() {
setLoading(true);
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
if (!cancelled) {
setUser(data);
setLoading(false);
}
}
fetchUser();
return () => {
cancelled = true;
};
}, [userId]);
// 订阅事件
useEffect(() => {
const handleResize = () => {
console.log('Window resized');
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
if (loading) return <p>Loading...</p>;
if (!user) return <p>User not found</p>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
2.4 useLayoutEffect #
jsx
import { useLayoutEffect, useRef } from 'preact/hooks';
function Tooltip({ content }) {
const tooltipRef = useRef(null);
// 在 DOM 更新后同步执行
useLayoutEffect(() => {
const tooltip = tooltipRef.current;
const { width } = tooltip.getBoundingClientRect();
tooltip.style.left = `-${width / 2}px`;
}, [content]);
return (
<div ref={tooltipRef} class="tooltip">
{content}
</div>
);
}
三、类组件生命周期 #
3.1 挂载阶段 #
jsx
import { Component } from 'preact';
class Example extends Component {
// 初始化 state
state = { count: 0 };
// 组件挂载前(已废弃,不推荐使用)
componentWillMount() {
console.log('Component will mount');
}
// 组件挂载后
componentDidMount() {
console.log('Component mounted');
// 适合:数据获取、订阅事件、DOM 操作
this.fetchData();
}
async fetchData() {
const response = await fetch('/api/data');
const data = await response.json();
this.setState({ data });
}
render() {
return <div>{this.state.count}</div>;
}
}
3.2 更新阶段 #
jsx
class Example extends Component {
// 是否应该更新
shouldComponentUpdate(nextProps, nextState) {
// 返回 false 阻止更新
return this.props.id !== nextProps.id;
}
// 组件更新前
componentWillUpdate(nextProps, nextState) {
console.log('Component will update');
}
// 获取更新前的快照
getSnapshotBeforeUpdate(prevProps, prevState) {
if (prevProps.items.length < this.props.items.length) {
return this.listRef.current.scrollHeight;
}
return null;
}
// 组件更新后
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('Component updated');
if (snapshot !== null) {
this.listRef.current.scrollTop += snapshot;
}
}
render() {
return <div ref={this.listRef}>{/* ... */}</div>;
}
}
3.3 卸载阶段 #
jsx
class Example extends Component {
componentDidMount() {
window.addEventListener('resize', this.handleResize);
this.startPolling();
}
componentWillUnmount() {
// 清理工作
window.removeEventListener('resize', this.handleResize);
this.stopPolling();
console.log('Component will unmount');
}
handleResize = () => {
console.log('Window resized');
};
startPolling() {
this.pollInterval = setInterval(() => {
this.fetchData();
}, 5000);
}
stopPolling() {
clearInterval(this.pollInterval);
}
render() {
return <div>Example</div>;
}
}
四、生命周期对比 #
4.1 类组件 vs 函数组件 #
| 类组件 | 函数组件 (Hooks) |
|---|---|
| componentDidMount | useEffect(() => {}, []) |
| componentDidUpdate | useEffect(() => {}, [deps]) |
| componentWillUnmount | useEffect(() => return () => {}, []) |
| shouldComponentUpdate | React.memo / useMemo |
| getSnapshotBeforeUpdate | useLayoutEffect |
4.2 转换示例 #
jsx
// 类组件
class UserProfile extends Component {
state = { user: null };
componentDidMount() {
this.fetchUser();
}
componentDidUpdate(prevProps) {
if (prevProps.userId !== this.props.userId) {
this.fetchUser();
}
}
async fetchUser() {
const response = await fetch(`/api/users/${this.props.userId}`);
const user = await response.json();
this.setState({ user });
}
render() {
if (!this.state.user) return <p>Loading...</p>;
return <div>{this.state.user.name}</div>;
}
}
// 函数组件
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
async function fetchUser() {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
setUser(data);
}
fetchUser();
}, [userId]);
if (!user) return <p>Loading...</p>;
return <div>{user.name}</div>;
}
五、常见模式 #
5.1 数据获取 #
jsx
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false;
async function fetchData() {
try {
setLoading(true);
const response = await fetch(url);
if (!response.ok) {
throw new Error('Network error');
}
const json = await response.json();
if (!cancelled) {
setData(json);
setError(null);
}
} catch (e) {
if (!cancelled) {
setError(e.message);
}
} finally {
if (!cancelled) {
setLoading(false);
}
}
}
fetchData();
return () => {
cancelled = true;
};
}, [url]);
return { data, loading, error };
}
5.2 事件订阅 #
jsx
function useWindowSize() {
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 size;
}
5.3 定时器 #
jsx
function useInterval(callback, delay) {
const savedCallback = useRef(callback);
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
if (delay === null) return;
const id = setInterval(() => savedCallback.current(), delay);
return () => clearInterval(id);
}, [delay]);
}
// 使用
function Counter() {
const [count, setCount] = useState(0);
useInterval(() => {
setCount(c => c + 1);
}, 1000);
return <h1>{count}</h1>;
}
5.4 之前的值 #
jsx
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
// 使用
function Counter() {
const [count, setCount] = useState(0);
const prevCount = usePrevious(count);
return (
<div>
<p>Current: {count}</p>
<p>Previous: {prevCount}</p>
<button onClick={() => setCount(c => c + 1)}>+</button>
</div>
);
}
六、错误处理 #
6.1 错误边界(类组件) #
jsx
class ErrorBoundary extends Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('Error caught:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div class="error">
<h1>Something went wrong.</h1>
<p>{this.state.error.message}</p>
</div>
);
}
return this.props.children;
}
}
// 使用
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
七、最佳实践 #
7.1 避免内存泄漏 #
jsx
function DataFetcher({ url }) {
const [data, setData] = useState(null);
useEffect(() => {
let cancelled = false;
fetch(url)
.then(res => res.json())
.then(data => {
if (!cancelled) {
setData(data);
}
});
return () => {
cancelled = true;
};
}, [url]);
return <div>{/* ... */}</div>;
}
7.2 正确设置依赖 #
jsx
// 错误:缺少依赖
useEffect(() => {
fetchData(userId);
}, []); // userId 变化时不会重新获取
// 正确:包含所有依赖
useEffect(() => {
fetchData(userId);
}, [userId]);
7.3 分离关注点 #
jsx
function Example({ userId }) {
const [user, setUser] = useState(null);
const [windowSize, setWindowSize] = useState({ width: 0, height: 0 });
// 用户数据获取
useEffect(() => {
fetchUser(userId).then(setUser);
}, [userId]);
// 窗口大小监听
useEffect(() => {
const handleResize = () => {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight
});
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return <div>{/* ... */}</div>;
}
八、总结 #
| 阶段 | 类组件方法 | 函数组件 Hook |
|---|---|---|
| 挂载 | componentDidMount | useEffect(…, []) |
| 更新 | componentDidUpdate | useEffect(…, [deps]) |
| 卸载 | componentWillUnmount | useEffect return |
| 渲染前 | getSnapshotBeforeUpdate | useLayoutEffect |
核心要点:
- 函数组件使用 useEffect 处理副作用
- 记得清理副作用避免内存泄漏
- 正确设置依赖数组
- 分离不同关注点到独立的 useEffect
最后更新:2026-03-28