React 生命周期
概述
React 组件的生命周期是指组件从创建到销毁的完整过程。理解生命周期对于优化组件性能、处理副作用和管理资源至关重要。本章将学习函数组件的 useEffect Hook 和类组件的生命周期方法。
🔄 函数组件生命周期(Hooks)
useEffect 基础
jsx
import React, { useState, useEffect } from 'react';
function BasicLifecycle() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
// 组件挂载和更新时都会执行
useEffect(() => {
console.log('组件渲染了');
document.title = `计数: ${count}`;
});
// 只在组件挂载时执行一次
useEffect(() => {
console.log('组件挂载了');
// 模拟从 API 获取数据
fetch('/api/user')
.then(response => response.json())
.then(data => setName(data.name))
.catch(error => console.error('获取数据失败:', error));
}, []); // 空依赖数组
// 只在 count 变化时执行
useEffect(() => {
console.log('count 变化了:', count);
if (count > 5) {
alert('计数超过 5 了!');
}
}, [count]); // count 依赖
return (
<div>
<h2>生命周期演示</h2>
<p>姓名: {name}</p>
<p>计数: {count}</p>
<button onClick={() => setCount(count + 1)}>
增加计数
</button>
</div>
);
}清理副作用
jsx
function CleanupExample() {
const [seconds, setSeconds] = useState(0);
const [isRunning, setIsRunning] = useState(false);
useEffect(() => {
let interval = null;
if (isRunning) {
console.log('启动定时器');
interval = setInterval(() => {
setSeconds(seconds => seconds + 1);
}, 1000);
}
// 清理函数
return () => {
if (interval) {
console.log('清理定时器');
clearInterval(interval);
}
};
}, [isRunning]);
// 组件卸载时的清理
useEffect(() => {
const handleBeforeUnload = () => {
console.log('页面即将卸载');
};
window.addEventListener('beforeunload', handleBeforeUnload);
return () => {
console.log('移除事件监听器');
window.removeEventListener('beforeunload', handleBeforeUnload);
};
}, []);
const reset = () => {
setSeconds(0);
setIsRunning(false);
};
return (
<div>
<h2>定时器: {seconds}s</h2>
<button onClick={() => setIsRunning(!isRunning)}>
{isRunning ? '暂停' : '开始'}
</button>
<button onClick={reset}>重置</button>
</div>
);
}数据获取和状态管理
jsx
function DataFetching() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [page, setPage] = useState(1);
useEffect(() => {
let isCancelled = false;
const fetchUsers = async () => {
try {
setLoading(true);
setError(null);
// 模拟 API 调用
await new Promise(resolve => setTimeout(resolve, 1000));
const response = await fetch(`/api/users?page=${page}`);
const data = await response.json();
// 检查组件是否仍然挂载
if (!isCancelled) {
setUsers(data.users);
setLoading(false);
}
} catch (err) {
if (!isCancelled) {
setError(err.message);
setLoading(false);
}
}
};
fetchUsers();
// 清理函数,防止内存泄漏
return () => {
isCancelled = true;
};
}, [page]); // 依赖 page,当 page 变化时重新获取数据
if (loading) {
return <div>加载中...</div>;
}
if (error) {
return <div>错误: {error}</div>;
}
return (
<div>
<h2>用户列表</h2>
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
<button
onClick={() => setPage(page - 1)}
disabled={page <= 1}
>
上一页
</button>
<span> 第 {page} 页 </span>
<button onClick={() => setPage(page + 1)}>
下一页
</button>
</div>
);
}🏗️ 类组件生命周期
挂载阶段
jsx
class MountingLifecycle extends React.Component {
constructor(props) {
super(props);
console.log('1. constructor: 组件构造');
this.state = {
data: null,
loading: true
};
}
static getDerivedStateFromProps(nextProps, prevState) {
console.log('2. getDerivedStateFromProps: 从 props 派生状态');
// 返回新状态或 null
return null;
}
componentDidMount() {
console.log('4. componentDidMount: 组件挂载完成');
// 在这里进行 API 调用、订阅事件等
this.fetchData();
}
render() {
console.log('3. render: 渲染组件');
const { data, loading } = this.state;
if (loading) {
return <div>加载中...</div>;
}
return (
<div>
<h2>类组件挂载演示</h2>
<p>数据: {data}</p>
<button onClick={this.handleRefresh}>刷新数据</button>
</div>
);
}
fetchData = async () => {
try {
// 模拟 API 调用
await new Promise(resolve => setTimeout(resolve, 1000));
this.setState({
data: '从服务器获取的数据',
loading: false
});
} catch (error) {
console.error('数据获取失败:', error);
this.setState({ loading: false });
}
};
handleRefresh = () => {
this.setState({ loading: true });
this.fetchData();
};
}更新阶段
jsx
class UpdatingLifecycle extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
name: 'React'
};
}
static getDerivedStateFromProps(nextProps, prevState) {
console.log('getDerivedStateFromProps: props 变化');
return null;
}
shouldComponentUpdate(nextProps, nextState) {
console.log('shouldComponentUpdate: 是否应该更新');
// 返回 false 可以阻止组件更新
// 这里我们阻止 count 为偶数时的更新(仅作演示)
return nextState.count % 2 !== 0 || nextState.name !== this.state.name;
}
render() {
console.log('render: 重新渲染');
const { count, name } = this.state;
return (
<div>
<h2>类组件更新演示</h2>
<p>计数: {count}</p>
<p>名称: {name}</p>
<button onClick={this.incrementCount}>增加计数</button>
<button onClick={this.changeName}>改变名称</button>
<p><small>注意:偶数计数不会触发视图更新</small></p>
</div>
);
}
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log('getSnapshotBeforeUpdate: 更新前快照');
// 返回值会传递给 componentDidUpdate
if (prevState.count !== this.state.count) {
return { prevCount: prevState.count };
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('componentDidUpdate: 组件更新完成');
if (snapshot && snapshot.prevCount !== undefined) {
console.log(`计数从 ${snapshot.prevCount} 更新到 ${this.state.count}`);
}
}
incrementCount = () => {
this.setState(prevState => ({ count: prevState.count + 1 }));
};
changeName = () => {
this.setState({
name: this.state.name === 'React' ? 'Vue' : 'React'
});
};
}卸载阶段
jsx
class UnmountingExample extends React.Component {
constructor(props) {
super(props);
this.state = { seconds: 0 };
this.interval = null;
}
componentDidMount() {
console.log('组件挂载,启动定时器');
this.interval = setInterval(() => {
this.setState(prevState => ({ seconds: prevState.seconds + 1 }));
}, 1000);
// 添加事件监听器
window.addEventListener('resize', this.handleResize);
}
componentWillUnmount() {
console.log('组件即将卸载,清理资源');
// 清理定时器
if (this.interval) {
clearInterval(this.interval);
}
// 移除事件监听器
window.removeEventListener('resize', this.handleResize);
// 取消网络请求
// this.abortController.abort();
}
handleResize = () => {
console.log('窗口大小改变');
};
render() {
return (
<div>
<h2>运行时间: {this.state.seconds}s</h2>
<p>调整窗口大小查看事件监听器</p>
</div>
);
}
}
// 容器组件,用于演示卸载
function UnmountingContainer() {
const [showComponent, setShowComponent] = useState(true);
return (
<div>
<button onClick={() => setShowComponent(!showComponent)}>
{showComponent ? '卸载组件' : '挂载组件'}
</button>
{showComponent && <UnmountingExample />}
</div>
);
}🔧 自定义 Hooks
封装生命周期逻辑
jsx
// 自定义 Hook:组件挂载状态
function useIsMounted() {
const isMounted = useRef(false);
useEffect(() => {
isMounted.current = true;
return () => {
isMounted.current = false;
};
}, []);
return isMounted;
}
// 自定义 Hook:防抖
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
// 自定义 Hook:数据获取
function useApi(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const isMounted = useIsMounted();
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
setError(null);
const response = await fetch(url);
const result = await response.json();
if (isMounted.current) {
setData(result);
}
} catch (err) {
if (isMounted.current) {
setError(err.message);
}
} finally {
if (isMounted.current) {
setLoading(false);
}
}
};
if (url) {
fetchData();
}
}, [url, isMounted]);
return { data, loading, error };
}
// 使用自定义 Hooks
function CustomHooksExample() {
const [searchTerm, setSearchTerm] = useState('');
const debouncedSearchTerm = useDebounce(searchTerm, 500);
const { data, loading, error } = useApi(
debouncedSearchTerm ? `/api/search?q=${debouncedSearchTerm}` : null
);
return (
<div>
<h2>自定义 Hooks 演示</h2>
<input
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="输入搜索内容..."
style={{ padding: '8px', width: '200px' }}
/>
{loading && <p>搜索中...</p>}
{error && <p>错误: {error}</p>}
{data && (
<div>
<h3>搜索结果:</h3>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
)}
</div>
);
}🎯 生命周期最佳实践
性能优化
jsx
function PerformanceOptimization() {
const [posts, setPosts] = useState([]);
const [currentUser, setCurrentUser] = useState(null);
const [theme, setTheme] = useState('light');
// 避免在每次渲染时创建新的对象
const memoizedUser = useMemo(() => {
return currentUser ? {
...currentUser,
fullName: `${currentUser.firstName} ${currentUser.lastName}`
} : null;
}, [currentUser]);
// 缓存昂贵的计算
const expensiveValue = useMemo(() => {
console.log('执行昂贵的计算');
return posts.reduce((total, post) => total + post.likes, 0);
}, [posts]);
// 缓存回调函数
const handlePostLike = useCallback((postId) => {
setPosts(posts => posts.map(post =>
post.id === postId
? { ...post, likes: post.likes + 1 }
: post
));
}, []);
// 获取数据
useEffect(() => {
const fetchPosts = async () => {
try {
const response = await fetch('/api/posts');
const data = await response.json();
setPosts(data);
} catch (error) {
console.error('获取帖子失败:', error);
}
};
fetchPosts();
}, []);
return (
<div className={`app-${theme}`}>
<h2>性能优化演示</h2>
{memoizedUser && (
<div>欢迎, {memoizedUser.fullName}!</div>
)}
<div>总点赞数: {expensiveValue}</div>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
切换主题
</button>
<PostList posts={posts} onLike={handlePostLike} />
</div>
);
}
// 被优化的子组件
const PostList = React.memo(function PostList({ posts, onLike }) {
console.log('PostList 渲染');
return (
<div>
{posts.map(post => (
<PostItem key={post.id} post={post} onLike={onLike} />
))}
</div>
);
});
const PostItem = React.memo(function PostItem({ post, onLike }) {
console.log('PostItem 渲染:', post.title);
return (
<div style={{ border: '1px solid #ddd', padding: '10px', margin: '10px 0' }}>
<h3>{post.title}</h3>
<p>点赞: {post.likes}</p>
<button onClick={() => onLike(post.id)}>
👍 点赞
</button>
</div>
);
});错误边界
jsx
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
// 更新状态以显示错误 UI
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
// 记录错误到错误报告服务
console.error('错误边界捕获到错误:', error, errorInfo);
// 可以将错误发送到错误监控服务
// logErrorToService(error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div style={{ padding: '20px', border: '1px solid red', margin: '10px' }}>
<h2>出现了错误</h2>
<p>应用遇到了一个错误,请刷新页面重试。</p>
<details>
<summary>错误详情</summary>
<pre>{this.state.error && this.state.error.toString()}</pre>
</details>
<button onClick={() => window.location.reload()}>
刷新页面
</button>
</div>
);
}
return this.props.children;
}
}
// 可能出错的组件
function BuggyComponent() {
const [count, setCount] = useState(0);
if (count > 3) {
throw new Error('计数器爆炸了!');
}
return (
<div>
<p>计数: {count}</p>
<button onClick={() => setCount(count + 1)}>
增加计数(超过 3 会出错)
</button>
</div>
);
}
// 使用错误边界
function ErrorBoundaryExample() {
return (
<div>
<h2>错误边界演示</h2>
<ErrorBoundary>
<BuggyComponent />
</ErrorBoundary>
</div>
);
}📝 本章小结
通过本章学习,你应该掌握了:
函数组件生命周期
- ✅ useEffect Hook 的使用方法
- ✅ 依赖数组的作用和最佳实践
- ✅ 清理副作用防止内存泄漏
- ✅ 自定义 Hooks 封装生命周期逻辑
类组件生命周期
- ✅ 挂载、更新、卸载三个阶段
- ✅ 各个生命周期方法的用途
- ✅ 性能优化和错误处理
- ✅ 从类组件到函数组件的迁移
最佳实践
- 资源清理:及时清理定时器、事件监听器
- 依赖优化:正确设置 useEffect 依赖
- 性能监控:使用 React DevTools Profiler
- 错误处理:实现错误边界组件
- 代码组织:使用自定义 Hooks 复用逻辑
继续学习:下一章 - React 组件间通信