React生命周期
下面,我们来系统的梳理关于 类组件生命周期 的基本知识点:
一、基础概念
1.1 什么是生命周期?
生命周期(Lifecycle)指的是组件从创建到销毁的整个过程。React类组件提供了一系列生命周期方法(也称为生命周期钩子),允许开发者在组件的不同阶段执行自定义代码。
1.2 生命周期的三个阶段
- 挂载阶段(Mounting):组件被创建并插入DOM
- 更新阶段(Updating):组件因props或state变化而重新渲染
- 卸载阶段(Unmounting):组件从DOM中移除
二、生命周期方法详解
2.1 挂载阶段(Mounting)
2.1.1 constructor()
- 调用时机:组件初始化时调用
- 主要用途:
- 初始化state
- 绑定事件处理方法
- 注意事项:
- 必须首先调用
super(props)
- 避免在此方法中引入副作用
- 必须首先调用
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则不更新
- 此方法无权访问组件实例
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
- 注意事项:
- 应为纯函数,不修改组件状态
- 不直接与浏览器交互
class Welcome extends React.Component {render() {return <h1>Hello, {this.props.name}</h1>;}
}
2.1.4 componentDidMount()
- 调用时机:组件挂载后(插入DOM树中)立即调用
- 主要用途:
- 执行DOM操作
- 发起网络请求
- 设置订阅
- 注意事项:此方法中可以调用setState,但会触发额外渲染
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
- 用于性能优化,避免不必要的渲染
- 不建议深层比较
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()
- 此方法不常用
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,但必须包裹在条件语句中
- 避免无限循环
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()
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
- 注意事项:
- 在渲染阶段调用,不允许执行副作用
- 仅用于捕获渲染/生命周期错误
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()
- 调用时机:后代组件抛出错误后调用
- 主要用途:
- 记录错误信息
- 执行副作用操作
- 注意事项:
- 在提交阶段调用,可以执行副作用
- 仅用于捕获渲染/生命周期错误
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 挂载过程
3.2 更新过程
3.3 卸载过程
四、生命周期最佳实践
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 数据获取组件
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 滚动位置保持组件
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 错误边界组件
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
componentDidMount() {this.timer = setInterval(() => {this.setState({ time: Date.now() });}, 1000);
}componentWillUnmount() {clearInterval(this.timer);
}
7.2 避免无限循环
问题:在componentDidUpdate中无条件调用setState
componentDidUpdate(prevProps) {// 错误:无条件调用setState// this.setState({ count: this.state.count + 1 });// 正确:添加条件判断if (this.props.id !== prevProps.id) {this.fetchData();}
}
7.3 正确处理异步操作
问题:组件卸载后处理异步回调
componentDidMount() {this._isMounted = true;fetchData().then(data => {if (this._isMounted) {this.setState({ data });}});
}componentWillUnmount() {this._isMounted = false;
}
八、生命周期图解总结
总结
- 理解三大阶段:挂载、更新、卸载
- 掌握核心方法:
- constructor:初始化状态和绑定方法
- componentDidMount:执行副作用操作
- shouldComponentUpdate:优化性能
- componentDidUpdate:响应更新
- componentWillUnmount:清理资源
- 使用错误边界:捕获组件树中的错误
- 遵循最佳实践:
- 避免在render中执行副作用
- 在componentWillUnmount中清理资源
- 使用条件判断防止无限循环
- 与现代React结合:
- 了解生命周期方法与Hooks的对应关系
- 避免使用已废弃的生命周期方法