下面,我们来系统的梳理关于 类组件生命周期 的基本知识点:
一、基础概念
1.1 什么是生命周期?
生命周期(Lifecycle)指的是组件从创建到销毁的整个过程。React类组件提供了一系列生命周期方法(也称为生命周期钩子),允许开发者在组件的不同阶段执行自定义代码。
1.2 生命周期的三个阶段
挂载阶段 更新阶段 卸载阶段
- 挂载阶段(Mounting):组件被创建并插入DOM
- 更新阶段(Updating):组件因props或state变化而重新渲染
- 卸载阶段(Unmounting):组件从DOM中移除
二、生命周期方法详解
2.1 挂载阶段(Mounting)
2.1.1 constructor()
- 调用时机:组件初始化时调用
- 主要用途 :
- 初始化state
- 绑定事件处理方法
- 注意事项 :
- 必须首先调用
super(props)
- 避免在此方法中引入副作用
- 必须首先调用
jsx
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
this.handleClick = this.handleClick.bind(this);
}
// ...
}
2.1.2 static getDerivedStateFromProps()
- 调用时机:在调用render方法之前调用,并且在初始挂载及后续更新时都会被调用
- 主要用途:使组件在props变化时更新state
- 注意事项 :
- 应返回一个对象来更新state,如果返回null则不更新
- 此方法无权访问组件实例
jsx
class ScrollPosition extends React.Component {
state = { scrollY: 0 };
static getDerivedStateFromProps(props, state) {
if (props.resetPosition) {
return { scrollY: 0 };
}
return null;
}
// ...
}
2.1.3 render()
- 调用时机:必须实现的方法,负责返回JSX
- 主要用途:渲染组件UI
- 注意事项 :
- 应为纯函数,不修改组件状态
- 不直接与浏览器交互
jsx
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
2.1.4 componentDidMount()
- 调用时机:组件挂载后(插入DOM树中)立即调用
- 主要用途 :
- 执行DOM操作
- 发起网络请求
- 设置订阅
- 注意事项:此方法中可以调用setState,但会触发额外渲染
jsx
class DataFetcher extends React.Component {
componentDidMount() {
fetch(this.props.url)
.then(res => res.json())
.then(data => this.setState({ data }));
}
// ...
}
2.2 更新阶段(Updating)
2.2.1 static getDerivedStateFromProps()
- 同挂载阶段,在每次更新前也会被调用
2.2.2 shouldComponentUpdate()
- 调用时机:在重新渲染前触发
- 主要用途:通过返回true/false控制组件是否更新
- 注意事项 :
- 默认返回true
- 用于性能优化,避免不必要的渲染
- 不建议深层比较
jsx
class PureComponentLike extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
// 仅当count变化时才更新
return nextState.count !== this.state.count;
}
// ...
}
2.2.3 render()
- 同挂载阶段,负责渲染更新
2.2.4 getSnapshotBeforeUpdate()
- 调用时机:在最近一次渲染输出(提交到DOM节点)之前调用
- 主要用途:捕获渲染前的DOM信息(如滚动位置)
- 注意事项 :
- 返回值将作为参数传递给componentDidUpdate()
- 此方法不常用
jsx
class ScrollList extends React.Component {
getSnapshotBeforeUpdate(prevProps, prevState) {
if (prevProps.items.length < this.props.items.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
// ...
}
2.2.5 componentDidUpdate()
- 调用时机:更新完成后立即调用(首次渲染不会调用)
- 主要用途 :
- 执行DOM操作
- 进行网络请求(需比较props)
- 注意事项 :
- 可以调用setState,但必须包裹在条件语句中
- 避免无限循环
jsx
class DataUpdater extends React.Component {
componentDidUpdate(prevProps) {
if (this.props.id !== prevProps.id) {
fetch(`/data/${this.props.id}`)
.then(res => res.json())
.then(data => this.setState({ data }));
}
}
// ...
}
2.3 卸载阶段(Unmounting)
2.3.1 componentWillUnmount()
- 调用时机:组件卸载及销毁之前调用
- 主要用途 :
- 清理定时器
- 取消网络请求
- 移除事件监听
- 清理订阅
- 注意事项:不应调用setState()
jsx
class TimerComponent extends React.Component {
componentDidMount() {
this.timerID = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
// ...
}
2.4 错误处理
2.4.1 static getDerivedStateFromError()
- 调用时机:后代组件抛出错误后调用
- 主要用途:返回一个值以更新state,用于渲染降级UI
- 注意事项 :
- 在渲染阶段调用,不允许执行副作用
- 仅用于捕获渲染/生命周期错误
jsx
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <h1>发生错误,请重试</h1>;
}
return this.props.children;
}
}
2.4.2 componentDidCatch()
- 调用时机:后代组件抛出错误后调用
- 主要用途 :
- 记录错误信息
- 执行副作用操作
- 注意事项 :
- 在提交阶段调用,可以执行副作用
- 仅用于捕获渲染/生命周期错误
jsx
class ErrorBoundary extends React.Component {
state = { hasError: false };
componentDidCatch(error, info) {
logErrorToService(error, info);
this.setState({ hasError: true });
}
render() {
if (this.state.hasError) {
return <h1>发生错误,请重试</h1>;
}
return this.props.children;
}
}
三、生命周期执行顺序
3.1 挂载过程
Constructor getDerivedStateFromProps Render componentDidMount 初始化props/state 计算新state 渲染DOM 可触发setState Constructor getDerivedStateFromProps Render componentDidMount
3.2 更新过程
getDerivedStateFromProps shouldComponentUpdate Render getSnapshotBeforeUpdate componentDidUpdate 接收新props/state 返回true则继续 渲染UI 传递快照 可触发setState getDerivedStateFromProps shouldComponentUpdate Render getSnapshotBeforeUpdate componentDidUpdate
3.3 卸载过程
componentWillUnmount 执行清理操作 componentWillUnmount
四、生命周期最佳实践
4.1 数据获取
- 使用
componentDidMount
进行初始数据获取 - 使用
componentDidUpdate
进行基于props变化的数据更新
4.2 性能优化
- 使用
shouldComponentUpdate
避免不必要的渲染 - 使用
React.PureComponent
自动浅比较props和state
4.3 资源清理
- 在
componentWillUnmount
中清除所有副作用资源
4.4 错误处理
- 使用错误边界(Error Boundaries)捕获组件树错误
五、生命周期与现代React
5.1 生命周期方法与Hooks对比
生命周期方法 | Hook 替代方案 |
---|---|
constructor | useState 初始化 |
componentDidMount | useEffect(() => {}, []) |
componentDidUpdate | useEffect(() => {}, [deps]) |
componentWillUnmount | useEffect 返回清理函数 |
shouldComponentUpdate | React.memo |
5.2 废弃的生命周期方法
React 17+ 已废弃以下方法:
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
替代方案:
- 使用
static getDerivedStateFromProps
替代componentWillReceiveProps
- 使用
getSnapshotBeforeUpdate
替代componentWillUpdate
六、实战案例
6.1 数据获取组件
jsx
class DataLoader extends React.Component {
state = {
data: null,
isLoading: true,
error: null
};
componentDidMount() {
this.fetchData();
}
componentDidUpdate(prevProps) {
if (prevProps.url !== this.props.url) {
this.fetchData();
}
}
componentWillUnmount() {
// 如果请求未完成,可以在这里中止
}
async fetchData() {
this.setState({ isLoading: true, error: null });
try {
const response = await fetch(this.props.url);
const data = await response.json();
this.setState({ data, isLoading: false });
} catch (error) {
this.setState({ error, isLoading: false });
}
}
render() {
const { data, isLoading, error } = this.state;
if (isLoading) return <Spinner />;
if (error) return <ErrorDisplay error={error} />;
return <DataView data={data} />;
}
}
6.2 滚动位置保持组件
jsx
class ScrollPreserver extends React.Component {
listRef = React.createRef();
getSnapshotBeforeUpdate(prevProps) {
if (prevProps.items.length < this.props.items.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
render() {
return (
<div ref={this.listRef} style={{ overflowY: 'scroll', height: 300 }}>
{this.props.items.map(item => (
<div key={item.id}>{item.text}</div>
))}
</div>
);
}
}
6.3 错误边界组件
jsx
class ErrorBoundary extends React.Component {
state = { hasError: false, error: null, errorInfo: null };
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
this.setState({ error, errorInfo });
logErrorToService(error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div className="error-boundary">
<h2>应用发生错误</h2>
<details style={{ whiteSpace: 'pre-wrap' }}>
{this.state.error && this.state.error.toString()}
<br />
{this.state.errorInfo.componentStack}
</details>
<button onClick={() => window.location.reload()}>重新加载</button>
</div>
);
}
return this.props.children;
}
}
七、常见问题与解决方案
7.1 避免内存泄漏
问题:组件卸载后调用setState
jsx
componentDidMount() {
this.timer = setInterval(() => {
this.setState({ time: Date.now() });
}, 1000);
}
componentWillUnmount() {
clearInterval(this.timer);
}
7.2 避免无限循环
问题:在componentDidUpdate中无条件调用setState
jsx
componentDidUpdate(prevProps) {
// 错误:无条件调用setState
// this.setState({ count: this.state.count + 1 });
// 正确:添加条件判断
if (this.props.id !== prevProps.id) {
this.fetchData();
}
}
7.3 正确处理异步操作
问题:组件卸载后处理异步回调
jsx
componentDidMount() {
this._isMounted = true;
fetchData().then(data => {
if (this._isMounted) {
this.setState({ data });
}
});
}
componentWillUnmount() {
this._isMounted = false;
}
八、生命周期图解总结
错误处理 卸载阶段 更新阶段 挂载阶段 true false getDerivedStateFromError componentDidCatch componentWillUnmount componentDidUpdate getDerivedStateFromProps componentDidMount constructor getDerivedStateFromProps render shouldComponentUpdate render getSnapshotBeforeUpdate 跳过渲染 render
总结
- 理解三大阶段:挂载、更新、卸载
- 掌握核心方法 :
- constructor:初始化状态和绑定方法
- componentDidMount:执行副作用操作
- shouldComponentUpdate:优化性能
- componentDidUpdate:响应更新
- componentWillUnmount:清理资源
- 使用错误边界:捕获组件树中的错误
- 遵循最佳实践 :
- 避免在render中执行副作用
- 在componentWillUnmount中清理资源
- 使用条件判断防止无限循环
- 与现代React结合 :
- 了解生命周期方法与Hooks的对应关系
- 避免使用已废弃的生命周期方法