Appearance
React 生命周期
本文详细介绍React组件的生命周期方法,包括挂载、更新和卸载阶段的各个钩子函数及其使用场景。
生命周期概述
React组件的生命周期可以分为三个主要阶段:
- 挂载阶段(Mounting):组件被创建并插入到DOM中
- 更新阶段(Updating):组件重新渲染,更新DOM
- 卸载阶段(Unmounting):组件从DOM中移除
此外,React 16.3引入了一个新的阶段:
- 错误处理(Error Handling):捕获渲染过程中的错误
生命周期方法图示
挂载阶段:
constructor()
↓
static getDerivedStateFromProps()
↓
render()
↓
componentDidMount()
更新阶段:
static getDerivedStateFromProps()
↓
shouldComponentUpdate()
↓
render()
↓
getSnapshotBeforeUpdate()
↓
componentDidUpdate()
卸载阶段:
componentWillUnmount()
错误处理:
static getDerivedStateFromError()
↓
componentDidCatch()挂载阶段
constructor(props)
用途:初始化状态和绑定方法
调用时机:组件被创建时调用一次
注意事项:
- 必须调用
super(props) - 不要在这里引入副作用或订阅
- 是唯一可以直接赋值
this.state的地方
jsx
constructor(props) {
super(props);
// 初始化状态
this.state = {
counter: 0,
isActive: false
};
// 绑定事件处理函数
this.handleClick = this.handleClick.bind(this);
}static getDerivedStateFromProps(props, state)
用途:根据props更新state
调用时机:
- 组件初始挂载时
- 接收新的props时
- 调用setState()时
- 调用forceUpdate()时
注意事项:
- 是静态方法,不能访问
this - 应返回一个对象来更新state,或返回null表示不更新
- 不应该有副作用
jsx
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.selectedItem !== prevState.selectedItem) {
return {
selectedItem: nextProps.selectedItem,
itemDetails: null
};
}
return null; // 不需要更新state
}render()
用途:渲染组件
调用时机:每次组件需要渲染时
注意事项:
- 必须实现的唯一方法
- 应该是纯函数
- 不应该修改组件状态
- 不应该直接与浏览器交互
jsx
render() {
return (
<div>
<h1>{this.props.title}</h1>
<p>{this.state.message}</p>
<button onClick={this.handleClick}>
Click me
</button>
</div>
);
}componentDidMount()
用途:组件挂载后执行副作用
调用时机:组件插入DOM树后立即调用
常见用途:
- 发起网络请求
- 添加订阅
- 操作DOM
- 设置定时器
jsx
componentDidMount() {
// 发起网络请求
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => this.setState({ data }));
// 添加事件监听
window.addEventListener('resize', this.handleResize);
// 设置定时器
this.timerId = setInterval(() => {
this.setState(prevState => ({
counter: prevState.counter + 1
}));
}, 1000);
}更新阶段
shouldComponentUpdate(nextProps, nextState)
用途:控制组件是否重新渲染
调用时机:接收新的props或state时,在渲染前调用
返回值:
true:组件将重新渲染(默认行为)false:组件不会重新渲染
注意事项:
- 仅作为性能优化使用
- 不要依赖它来阻止渲染
- 考虑使用PureComponent代替手动实现
jsx
shouldComponentUpdate(nextProps, nextState) {
// 只有当id变化或counter增加时才重新渲染
return (
nextProps.id !== this.props.id ||
nextState.counter > this.state.counter
);
}getSnapshotBeforeUpdate(prevProps, prevState)
用途:在DOM更新前捕获一些信息
调用时机:render之后,实际DOM更新之前
返回值:任何值,将作为第三个参数传递给componentDidUpdate
常见用途:
- 保存滚动位置
- 保存DOM状态
jsx
getSnapshotBeforeUpdate(prevProps, prevState) {
// 捕获更新前的滚动位置
if (prevProps.list.length < this.props.list.length) {
const scrollContainer = this.listRef.current;
return scrollContainer.scrollHeight - scrollContainer.scrollTop;
}
return null;
}componentDidUpdate(prevProps, prevState, snapshot)
用途:组件更新后执行副作用
调用时机:组件更新后立即调用
注意事项:
- 首次渲染不会调用
- 可以在这里进行网络请求,但需要条件判断
- 可以在这里操作DOM
jsx
componentDidUpdate(prevProps, prevState, snapshot) {
// 检查是否有特定的prop变化
if (this.props.userId !== prevProps.userId) {
this.fetchData(this.props.userId);
}
// 使用getSnapshotBeforeUpdate的返回值
if (snapshot !== null) {
const scrollContainer = this.listRef.current;
scrollContainer.scrollTop = scrollContainer.scrollHeight - snapshot;
}
}卸载阶段
componentWillUnmount()
用途:清理组件
调用时机:组件从DOM中移除前立即调用
常见用途:
- 清除定时器
- 取消网络请求
- 清除订阅
jsx
componentWillUnmount() {
// 清除定时器
clearInterval(this.timerId);
// 移除事件监听
window.removeEventListener('resize', this.handleResize);
// 取消订阅
this.subscription.unsubscribe();
}错误处理
static getDerivedStateFromError(error)
用途:在子组件抛出错误后渲染备用UI
调用时机:后代组件抛出错误后调用
注意事项:
- 是静态方法,不能执行副作用
- 应返回一个对象来更新state
jsx
static getDerivedStateFromError(error) {
// 更新state使下一次渲染显示错误UI
return { hasError: true, error };
}componentDidCatch(error, info)
用途:记录错误信息
调用时机:后代组件抛出错误后调用
参数:
error:抛出的错误info:包含组件堆栈信息的对象
注意事项:
- 可以执行副作用,如记录错误
- 在开发模式下,错误会冒泡到window
jsx
componentDidCatch(error, info) {
// 记录错误到错误报告服务
logErrorToService(error, info.componentStack);
}错误边界示例
jsx
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
// 更新state使下一次渲染显示错误UI
return { hasError: true, error };
}
componentDidCatch(error, info) {
// 记录错误
console.error('Error caught by boundary:', error);
console.error('Component stack:', info.componentStack);
}
render() {
if (this.state.hasError) {
// 渲染错误UI
return (
<div className="error-container">
<h2>Something went wrong.</h2>
<details>
<summary>Error Details</summary>
<p>{this.state.error && this.state.error.toString()}</p>
</details>
<button onClick={() => this.setState({ hasError: false, error: null })}>
Try again
</button>
</div>
);
}
// 正常渲染子组件
return this.props.children;
}
}
// 使用错误边界
function App() {
return (
<div>
<h1>My Application</h1>
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
</div>
);
}已废弃的生命周期方法
以下生命周期方法在React 16.3后被标记为不安全,将在未来版本中移除:
componentWillMount()componentWillReceiveProps(nextProps)componentWillUpdate(nextProps, nextState)
这些方法已被重命名,添加了UNSAFE_前缀:
UNSAFE_componentWillMount()UNSAFE_componentWillReceiveProps(nextProps)UNSAFE_componentWillUpdate(nextProps, nextState)
建议使用新的生命周期方法代替:
- 使用
constructor和componentDidMount代替componentWillMount - 使用
static getDerivedStateFromProps代替componentWillReceiveProps - 使用
componentDidUpdate代替componentWillUpdate,如需在DOM更新前获取信息,使用getSnapshotBeforeUpdate
函数组件与Hooks
随着React Hooks的引入,函数组件现在可以使用类似生命周期的功能:
useState- 添加状态useEffect- 处理副作用,替代多个生命周期方法useContext- 消费上下文useReducer- 复杂状态管理useCallback- 记忆化回调函数useMemo- 记忆化计算结果useRef- 保存可变值和访问DOMuseLayoutEffect- 同步执行副作用useImperativeHandle- 自定义暴露给父组件的实例值useDebugValue- 在开发工具中显示自定义hook标签
类组件生命周期与Hooks对应关系
| 类组件生命周期 | Hooks等价实现 |
|---|---|
constructor | useState, useRef |
getDerivedStateFromProps | useState + useEffect |
shouldComponentUpdate | React.memo + useMemo |
render | 函数组件本身 |
componentDidMount | useEffect(() => {}, []) |
componentDidUpdate | useEffect(() => {}, [dependencies]) |
componentWillUnmount | useEffect(() => { return () => {} }, []) |
getSnapshotBeforeUpdate | 无直接等价物,可使用useRef+useLayoutEffect |
componentDidCatch, getDerivedStateFromError | 无直接等价物,需使用类组件错误边界 |
使用Hooks模拟生命周期
jsx
import React, { useState, useEffect, useRef, useLayoutEffect } from 'react';
function LifecycleComponent(props) {
console.log('Render/Re-render');
// 类似constructor
const [count, setCount] = useState(0);
const [data, setData] = useState(null);
// 类似componentDidMount
useEffect(() => {
console.log('Component mounted');
// 发起网络请求
fetch('https://api.example.com/data')
.then(response => response.json())
.then(result => setData(result));
// 设置定时器
const timerId = setInterval(() => {
setCount(c => c + 1);
}, 1000);
// 类似componentWillUnmount
return () => {
console.log('Component will unmount');
clearInterval(timerId);
};
}, []); // 空依赖数组 = 仅在挂载和卸载时运行
// 类似componentDidUpdate (特定依赖)
useEffect(() => {
if (count > 0) { // 跳过首次渲染
console.log('Count updated:', count);
}
}, [count]); // 仅在count变化时运行
// 类似getSnapshotBeforeUpdate + componentDidUpdate
const prevPropsRef = useRef();
useLayoutEffect(() => {
// 在DOM更新前保存上一个props
const prevProps = prevPropsRef.current;
// 在DOM更新后运行
if (prevProps && prevProps.id !== props.id) {
console.log('Props changed from', prevProps.id, 'to', props.id);
}
// 更新ref以便下次比较
prevPropsRef.current = props;
});
return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>Increment</button>
{data && <pre>{JSON.stringify(data, null, 2)}</pre>}
</div>
);
}最佳实践
生命周期方法使用建议
保持生命周期方法纯净
- 避免在
render中引入副作用 - 避免在
constructor中执行异步操作
- 避免在
合理使用
shouldComponentUpdate- 仅用于性能优化
- 考虑使用
React.PureComponent或React.memo
在正确的生命周期中执行操作
- 网络请求:
componentDidMount和componentDidUpdate - DOM操作:
componentDidMount、componentDidUpdate和getSnapshotBeforeUpdate - 订阅设置:
componentDidMount - 清理操作:
componentWillUnmount
- 网络请求:
使用错误边界捕获错误
- 将错误边界放在合适的层级
- 提供有意义的错误UI
迁移到新的生命周期方法
- 避免使用带有
UNSAFE_前缀的方法 - 使用静态
getDerivedStateFromProps代替componentWillReceiveProps - 使用
getSnapshotBeforeUpdate代替componentWillUpdate
- 避免使用带有
考虑使用Hooks
- 对于新组件,考虑使用函数组件+Hooks
- 将复杂逻辑提取到自定义Hooks中