React生命周期的演进历程
React生命周期方法经历了从类组件到函数组件的重大转变。理解这一演进过程不仅有助于维护老项目,更能深入理解React的设计哲学。
类组件生命周期全景图
1. 挂载阶段(Mounting)
class ComponentExample extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
console.log('1. constructor - 初始化state和绑定方法');
}
static getDerivedStateFromProps(nextProps, prevState) {
console.log('2. getDerivedStateFromProps - 在渲染前根据props调整state');
// 返回要更新的state,或null不更新
return nextProps.initialCount !== prevState.count
? { count: nextProps.initialCount }
: null;
}
componentDidMount() {
console.log('4. componentDidMount - 组件已挂载到DOM');
// 进行DOM操作、网络请求、订阅事件
this.timer = setInterval(() => {
this.setState(prev => ({ count: prev.count + 1 }));
}, 1000);
}
render() {
console.log('3. render - 渲染组件');
return <div>Count: {this.state.count}</div>;
}
}
2. 更新阶段(Updating)
class UpdateExample extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
console.log('1. shouldComponentUpdate - 决定是否重新渲染');
// 性能优化关键:避免不必要的渲染
return nextState.count !== this.state.count ||
nextProps.title !== this.props.title;
}
static getDerivedStateFromProps(nextProps, prevState) {
console.log('2. getDerivedStateFromProps - props变化时调整state');
return null;
}
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log('4. getSnapshotBeforeUpdate - 在DOM更新前捕获信息');
// 捕获滚动位置等DOM信息
if (prevState.items.length < this.state.items.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('5. componentDidUpdate - DOM更新完成后调用');
// 使用getSnapshotBeforeUpdate返回的信息
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
// 条件性执行副作用
if (this.props.userID !== prevProps.userID) {
this.fetchData(this.props.userID);
}
}
render() {
console.log('3. render - 重新渲染');
return <div ref={this.listRef}>/* 内容 */</div>;
}
}
3. 卸载阶段(Unmounting)
class UnmountExample extends React.Component {
componentWillUnmount() {
console.log('componentWillUnmount - 组件即将卸载');
// 清理工作:取消定时器、网络请求、事件订阅
clearInterval(this.timer);
this.subscription.unsubscribe();
}
}
函数组件的"生命周期"实现
useEffect:生命周期的现代化身
import { useState, useEffect, useRef } from 'react';
function FunctionComponentLifecycle({ userId, title }) {
const [count, setCount] = useState(0);
const [user, setUser] = useState(null);
const [data, setData] = useState([]);
const prevUserIdRef = useRef();
// 模拟 componentDidMount + componentWillUnmount
useEffect(() => {
console.log('1. useEffect - 挂载完成 (componentDidMount)');
const timer = setInterval(() => {
setCount(prev => prev + 1);
}, 1000);
// 清理函数 - 模拟 componentWillUnmount
return () => {
console.log('清理效果 - 组件卸载 (componentWillUnmount)');
clearInterval(timer);
};
}, []); // 空依赖数组 = 只在挂载和卸载时执行
// 模拟 getDerivedStateFromProps + componentDidUpdate
useEffect(() => {
console.log('2. useEffect - 依赖变化时执行');
if (prevUserIdRef.current !== userId) {
console.log('userId 变化,获取新数据');
fetchUserData(userId).then(setUser);
}
prevUserIdRef.current = userId;
}, [userId]); // 依赖数组包含userId
// 模拟所有渲染后的副作用 (componentDidUpdate)
useEffect(() => {
console.log('3. useEffect - 每次渲染后执行');
document.title = `${title} - Count: ${count}`;
}); // 没有依赖数组 = 每次渲染都执行
// 模拟 getSnapshotBeforeUpdate 的复杂实现
const listRef = useRef();
const snapshotRef = useRef();
// 在渲染前捕获快照
useEffect(() => {
if (listRef.current) {
const list = listRef.current;
snapshotRef.current = {
scrollHeight: list.scrollHeight,
scrollTop: list.scrollTop
};
}
});
// 在渲染后使用快照
useEffect(() => {
if (snapshotRef.current && listRef.current) {
const list = listRef.current;
const prevSnapshot = snapshotRef.current;
if (list.scrollHeight > prevSnapshot.scrollHeight) {
list.scrollTop = list.scrollTop +
(list.scrollHeight - prevSnapshot.scrollHeight);
}
}
});
return (
<div ref={listRef}>
<h1>{title}</h1>
<p>Count: {count}</p>
<p>User: {user?.name}</p>
</div>
);
}
关键生命周期方法深度解析
componentDidMount:组件挂载完成
使用场景:
- DOM操作
- 网络请求
- 事件订阅
- 定时器设置
class DataFetchingComponent extends React.Component {
state = { data: null, loading: true, error: null };
async componentDidMount() {
try {
console.log('开始获取数据...');
const response = await fetch(this.props.url);
if (!response.ok) throw new Error('Network response was not ok');
const data = await response.json();
this.setState({ data, loading: false });
// DOM操作示例
this.containerRef.current.focus();
} catch (error) {
this.setState({ error: error.message, loading: false });
}
}
render() {
const { data, loading, error } = this.state;
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return <div ref={this.containerRef}>{JSON.stringify(data)}</div>;
}
}
getSnapshotBeforeUpdate:DOM更新前的快照
独特价值:在DOM实际更新前捕获当前状态,用于处理滚动位置、文本选择等场景。
class ChatList extends React.Component {
state = { messages: [] };
listRef = React.createRef();
previousScrollHeight = null;
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log('捕获DOM更新前的快照');
const list = this.listRef.current;
// 如果正在添加新消息,捕获当前滚动信息
if (prevState.messages.length < this.state.messages.length) {
return {
scrollHeight: list.scrollHeight,
scrollTop: list.scrollTop,
wasAtBottom: this.isScrolledToBottom(list)
};
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('使用快照调整DOM');
if (snapshot) {
const list = this.listRef.current;
const wasAtBottom = snapshot.wasAtBottom;
// 如果用户原本在底部,保持滚动到底部
if (wasAtBottom) {
list.scrollTop = list.scrollHeight - list.clientHeight;
} else {
// 否则,保持之前的相对位置
const heightDiff = list.scrollHeight - snapshot.scrollHeight;
list.scrollTop = snapshot.scrollTop + heightDiff;
}
}
}
isScrolledToBottom(element) {
return element.scrollHeight - element.scrollTop === element.clientHeight;
}
addMessage = (text) => {
this.setState(prev => ({
messages: [...prev.messages, { id: Date.now(), text }]
}));
};
render() {
return (
<div ref={this.listRef} style={{ height: '300px', overflow: 'auto' }}>
{this.state.messages.map(msg => (
<div key={msg.id}>{msg.text}</div>
))}
</div>
);
}
}
生命周期的最佳实践与陷阱
1. 避免常见的生命周期误用
class AntiPatternsExample extends React.Component {
// ❌ 错误:在constructor中进行数据获取
constructor(props) {
super(props);
this.state = { data: null };
// fetchData(); // 不应该在这里进行副作用操作
}
// ❌ 错误:在render中设置state
render() {
// if (someCondition) this.setState(...) // 会导致无限循环
return <div>Content</div>;
}
// ✅ 正确:在componentDidMount中进行初始化操作
componentDidMount() {
this.fetchData();
this.setupSubscriptions();
}
// ✅ 正确:在componentDidUpdate中条件性执行副作用
componentDidUpdate(prevProps) {
if (this.props.userId !== prevProps.userId) {
this.fetchData(this.props.userId);
}
}
async fetchData(userId) {
// 数据获取逻辑
}
}
2. 性能优化:shouldComponentUpdate的正确使用
class OptimizedComponent extends React.Component {
state = { items: [], filter: '' };
shouldComponentUpdate(nextProps, nextState) {
// 精确控制重新渲染的条件
if (nextState.items === this.state.items &&
nextState.filter === this.state.filter &&
nextProps.theme === this.props.theme) {
return false; // 避免不必要的渲染
}
return true;
}
// 或者使用 PureComponent
// class OptimizedComponent extends React.PureComponent
}
// 函数组件等价实现
const OptimizedFunctionComponent = React.memo(
function MyComponent({ items, filter, theme }) {
// 组件逻辑
},
(prevProps, nextProps) => {
// 自定义比较函数
return prevProps.items === nextProps.items &&
prevProps.filter === nextProps.filter &&
prevProps.theme === nextProps.theme;
}
);
从类组件到函数组件的迁移策略
模式对比表
| 类组件生命周期 | 函数组件等价实现 | 说明 |
|---|---|---|
constructor | useState 初始化 | 状态初始化 |
componentDidMount | useEffect(fn, []) | 挂载后副作用 |
componentDidUpdate | useEffect(fn) | 更新后副作用 |
componentWillUnmount | useEffect(() => fn, []) | 清理函数 |
getDerivedStateFromProps | useState + useEffect | 基于props派生状态 |
getSnapshotBeforeUpdate | useRef + useEffect 组合 | DOM更新前快照 |
shouldComponentUpdate | React.memo | 性能优化 |
迁移示例:从类组件到函数组件
// 类组件版本
class UserProfile extends React.Component {
state = { user: null, loading: true };
componentDidMount() {
this.fetchUser(this.props.userId);
}
componentDidUpdate(prevProps) {
if (prevProps.userId !== this.props.userId) {
this.fetchUser(this.props.userId);
}
}
componentWillUnmount() {
this.ignore = true;
}
async fetchUser(userId) {
this.setState({ loading: true });
try {
const user = await api.getUser(userId);
if (!this.ignore) {
this.setState({ user, loading: false });
}
} catch (error) {
if (!this.ignore) {
this.setState({ loading: false, error });
}
}
}
render() {
const { user, loading, error } = this.state;
if (loading) return <Spinner />;
if (error) return <Error message={error.message} />;
return <Profile user={user} />;
}
}
// 函数组件版本
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let ignore = false;
async function fetchUser() {
setLoading(true);
setError(null);
try {
const userData = await api.getUser(userId);
if (!ignore) {
setUser(userData);
setLoading(false);
}
} catch (err) {
if (!ignore) {
setError(err);
setLoading(false);
}
}
}
fetchUser();
return () => {
ignore = true; // 清理函数
};
}, [userId]); // 依赖数组
if (loading) return <Spinner />;
if (error) return <Error message={error.message} />;
return <Profile user={user} />;
}
现代React开发的生命周期思考
心智模型的转变
从"生命周期时间点"转向"状态与副作用的关系":
// 旧的思维方式:关注"什么时候"
class OldMindset extends React.Component {
componentDidMount() { /* 挂载后做事 */ }
componentDidUpdate() { /* 更新后做事 */ }
componentWillUnmount() { /* 卸载前清理 */ }
}
// 新的思维方式:关注"什么状态变化时需要做什么"
function NewMindset({ userId }) {
// 状态声明
const [user, setUser] = useState(null);
// 副作用声明:当userId变化时,获取用户数据
useEffect(() => {
fetchUser(userId).then(setUser);
}, [userId]);
// 渲染基于状态
return user ? <Profile user={user} /> : <Spinner />;
}
总结
React生命周期从类组件的明确时间点方法,演进到函数组件的声明式副作用管理。理解这一演进不仅有助于代码迁移,更重要的是掌握现代React"状态驱动UI"的核心思想。
getSnapshotBeforeUpdate等高级生命周期方法在特定场景下仍有其价值,但在日常开发中,useEffect的组合使用已能覆盖绝大多数需求。掌握如何用函数组件的思维来思考组件行为,是现代React开发者的关键技能。
生命周期方法的演进反映了React从"如何做"到"做什么"的范式转变,这种转变让代码更声明式、更易于理解和维护。

1067

被折叠的 条评论
为什么被折叠?



