说说 React 生命周期有哪些不同阶段?每个阶段对应的方法是?
问题解析(面试官考察点)
面试官通过此问题主要考察:
- 对 React 组件生命周期的全面理解
- 各生命周期方法的执行时机和用途
- React 16.3+ 新版本生命周期的掌握
- 能否正确使用生命周期解决实际问题
核心概念(基础知识点)
什么是生命周期
生命周期指组件从创建到销毁的整个过程,React 在特定时机调用特定方法,让开发者可以介入处理。
三个阶段
- 创建阶段(Mounting):组件被创建并插入 DOM
- 更新阶段(Updating):组件重新渲染
- 卸载阶段(Unmounting):组件从 DOM 中移除
详细解答(代码示例)
创建阶段
class MyComponent extends React.Component {
// 1. constructor
constructor(props) {
super(props);
this.state = { count: 0 };
console.log('1. constructor');
}
// 2. getDerivedStateFromProps(静态方法)
static getDerivedStateFromProps(props, state) {
console.log('2. getDerivedStateFromProps');
// 返回新的 state 或 null
if (props.initialCount !== state.count) {
return { count: props.initialCount };
}
return null;
}
// 3. render
render() {
console.log('3. render');
return <div>{this.state.count}</div>;
}
// 4. componentDidMount
componentDidMount() {
console.log('4. componentDidMount');
// 执行副作用:数据获取、订阅等
}
}
更新阶段
class MyComponent extends React.Component {
// 1. getDerivedStateFromProps
static getDerivedStateFromProps(props, state) {
console.log('1. getDerivedStateFromProps');
return null;
}
// 2. shouldComponentUpdate
shouldComponentUpdate(nextProps, nextState) {
console.log('2. shouldComponentUpdate');
// 返回 true 则继续更新,false 则阻止更新
return nextState.count !== this.state.count;
}
// 3. render
render() {
console.log('3. render');
return <div>{this.state.count}</div>;
}
// 4. getSnapshotBeforeUpdate
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log('4. getSnapshotBeforeUpdate');
// 返回值传递给 componentDidUpdate
return 'snapshot data';
}
// 5. componentDidUpdate
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('5. componentDidUpdate, snapshot:', snapshot);
// 执行副作用
}
}
卸载阶段
class MyComponent extends React.Component {
componentWillUnmount() {
console.log('componentWillUnmount');
// 清理工作:取消订阅、清除定时器等
}
render() {
return <div>Content</div>;
}
}
深入理解(原理剖析)
完整生命周期流程图
创建阶段:
constructor → getDerivedStateFromProps → render → componentDidMount
更新阶段:
getDerivedStateFromProps → shouldComponentUpdate → render →
getSnapshotBeforeUpdate → componentDidUpdate
卸载阶段:
componentWillUnmount
各生命周期详解
constructor
constructor(props) {
super(props); // 必须调用
// 初始化 state
this.state = { count: 0 };
// 绑定事件处理函数
this.handleClick = this.handleClick.bind(this);
}
用途:
- 初始化 state
- 绑定事件处理函数
注意:
- 不要调用 setState
- 避免引入副作用
getDerivedStateFromProps
static getDerivedStateFromProps(props, state) {
// 根据 props 更新 state
if (props.userId !== state.prevUserId) {
return {
prevUserId: props.userId,
userData: null // 需要重新加载
};
}
return null; // 不更新 state
}
特点:
- 静态方法,无法访问 this
- 在创建和更新阶段都会调用
- 返回对象更新 state,返回 null 不更新
shouldComponentUpdate
shouldComponentUpdate(nextProps, nextState) {
// 性能优化:只有 count 变化才重新渲染
return nextState.count !== this.state.count;
}
用途:
- 性能优化
- 控制组件是否重新渲染
注意:
- 不要进行深层比较(影响性能)
- 不要调用 setState
render
render() {
// 返回 JSX(或 null)
return (
<div>
<h1>{this.state.title}</h1>
</div>
);
}
注意:
- 必须是纯函数
- 不要调用 setState(会导致死循环)
- 不要操作 DOM
getSnapshotBeforeUpdate
getSnapshotBeforeUpdate(prevProps, prevState) {
// 获取更新前的滚动位置
if (prevProps.list.length < this.props.list.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;
}
}
用途:
- 获取更新前的 DOM 信息
- 常用于保存滚动位置
componentDidMount
componentDidMount() {
// 数据获取
fetch('/api/data')
.then(res => res.json())
.then(data => this.setState({ data }));
// 订阅
this.subscription = someSource.subscribe();
// 操作 DOM
this.canvas.getContext('2d');
}
用途:
- 数据获取
- 订阅设置
- DOM 操作
componentDidUpdate
componentDidUpdate(prevProps, prevState) {
// props 变化时重新获取数据
if (prevProps.userId !== this.props.userId) {
this.fetchUserData(this.props.userId);
}
}
用途:
- 根据变化执行操作
- 网络请求
- DOM 操作
componentWillUnmount
componentWillUnmount() {
// 取消订阅
this.subscription.unsubscribe();
// 清除定时器
clearInterval(this.timer);
// 取消网络请求
this.controller.abort();
}
用途:
- 清理订阅
- 清除定时器
- 取消未完成的请求
新旧生命周期对比
React 16.3 之前(已废弃):
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
React 16.3+(新版本):
- getDerivedStateFromProps(替代 componentWillReceiveProps)
- getSnapshotBeforeUpdate(新增)
废弃原因:
- 在异步渲染模式下可能不安全
- 被滥用导致问题
// 旧版本
componentWillReceiveProps(nextProps) {
if (nextProps.id !== this.props.id) {
this.setState({ data: null });
fetchData(nextProps.id);
}
}
// 新版本
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.id !== prevState.prevId) {
return { prevId: nextProps.id, data: null };
}
return null;
}
componentDidUpdate(prevProps) {
if (prevProps.id !== this.props.id) {
fetchData(this.props.id);
}
}
函数组件中的生命周期(Hooks)
import React, { useState, useEffect, useRef } from 'react';
function MyComponent({ userId }) {
const [count, setCount] = useState(0);
const prevUserIdRef = useRef();
// 相当于 componentDidMount + componentDidUpdate
useEffect(() => {
console.log('组件挂载或更新');
return () => {
console.log('清理(组件卸载或依赖变化)');
};
}, [userId]);
// 相当于 componentDidMount
useEffect(() => {
console.log('只在挂载时执行');
fetchData();
return () => {
console.log('只在卸载时执行');
};
}, []);
// 获取 prevProps
useEffect(() => {
prevUserIdRef.current = userId;
});
const prevUserId = prevUserIdRef.current;
return <div>{count}</div>;
}
最佳实践
避免滥用 getDerivedStateFromProps
// 不推荐:过度使用派生 state
static getDerivedStateFromProps(props, state) {
return { value: props.value };
}
// 推荐:使用受控组件
function MyComponent({ value, onChange }) {
return <input value={value} onChange={onChange} />;
}
// 或完全非受控组件 + key
<MyInput key={userId} defaultValue={initialValue} />
正确使用 shouldComponentUpdate
// 使用 PureComponent(自动浅比较)
class MyComponent extends React.PureComponent {
render() {
return <div>{this.props.name}</div>;
}
}
// 或使用 React.memo(函数组件)
const MyComponent = React.memo(function MyComponent({ name }) {
return <div>{name}</div>;
});
数据获取的最佳位置
// 类组件
componentDidMount() {
this.loadData();
}
componentDidUpdate(prevProps) {
if (prevProps.id !== this.props.id) {
this.loadData();
}
}
// 函数组件
useEffect(() => {
loadData();
}, [id]);
清理副作用
componentDidMount() {
this.subscription = someSource.subscribe(
data => this.setState({ data })
);
}
componentWillUnmount() {
this.subscription.unsubscribe();
}
// 函数组件
useEffect(() => {
const subscription = someSource.subscribe(
data => setData(data)
);
return () => {
subscription.unsubscribe();
};
}, []);
面试要点
-
三个阶段
- 创建:constructor → getDerivedStateFromProps → render → componentDidMount
- 更新:getDerivedStateFromProps → shouldComponentUpdate → render → getSnapshotBeforeUpdate → componentDidUpdate
- 卸载:componentWillUnmount
-
新版本变化
- 废弃:componentWillMount、componentWillReceiveProps、componentWillUpdate
- 新增:getDerivedStateFromProps、getSnapshotBeforeUpdate
- 原因:异步渲染安全性
-
常用生命周期用途
- constructor:初始化 state
- componentDidMount:数据获取、订阅
- componentDidUpdate:根据 props 变化执行操作
- componentWillUnmount:清理工作
-
Hooks 对应关系
- useEffect 替代 componentDidMount、componentDidUpdate、componentWillUnmount
- useLayoutEffect 替代 componentDidMount、componentDidUpdate(同步执行)
-
常见面试题
- React 生命周期有哪些阶段?
- 为什么废弃 componentWillMount?
- getDerivedStateFromProps 的作用?
- 在哪些生命周期可以调用 setState?
- 函数组件如何实现生命周期?
-
注意事项
- constructor 和 render 中不要调用 setState
- shouldComponentUpdate 不要进行深层比较
- componentWillUnmount 必须清理副作用