Appearance
React 生命周期详解
React 组件的生命周期是指组件从创建到销毁的整个过程。不同版本的 React 对生命周期方法有不同的定义和调整。本文将详细介绍 React 各个版本的生命周期方法、作用及使用场景。
React 16.3 之前的生命周期
在 React 16.3 之前,生命周期可以分为三个阶段:挂载(Mounting)、更新(Updating)和卸载(Unmounting)。
挂载阶段
constructor(props)
- 作用:初始化组件的状态和绑定方法
- 使用场景:设置初始 state,绑定事件处理函数
- 注意:在构造函数中应避免调用 setState(),因为此时组件还未挂载
componentWillMount()
- 作用:组件即将挂载到 DOM 前调用
- 使用场景:进行一些初始化操作,但不推荐在其中进行 API 调用
- 注意:在 React 16.3 中被标记为过时,在 React 17 中被移除
render()
- 作用:渲染组件的 UI
- 使用场景:返回 JSX 或 null
- 注意:render 方法应该是纯函数,不应该修改组件状态
componentDidMount()
- 作用:组件挂载到 DOM 后调用
- 使用场景:执行 DOM 操作、发起 API 请求、订阅事件等
更新阶段
componentWillReceiveProps(nextProps)
- 作用:当组件接收新的 props 时调用
- 使用场景:根据新 props 更新 state
- 注意:在 React 16.3 中被标记为过时,在 React 17 中被移除
shouldComponentUpdate(nextProps, nextState)
- 作用:决定组件是否需要重新渲染
- 使用场景:性能优化,避免不必要的渲染
- 返回值:布尔值,true 表示需要更新,false 表示不需要
componentWillUpdate(nextProps, nextState)
- 作用:组件即将更新前调用
- 使用场景:准备更新所需的数据
- 注意:在 React 16.3 中被标记为过时,在 React 17 中被移除
render()
- 作用:重新渲染组件的 UI
componentDidUpdate(prevProps, prevState)
- 作用:组件更新后调用
- 使用场景:执行 DOM 操作、比较前后 props 或 state 的变化、发起 API 请求等
卸载阶段
- componentWillUnmount()
- 作用:组件即将从 DOM 中卸载前调用
- 使用场景:清理定时器、取消订阅、清理事件监听器等
React 16.3 及之后的生命周期
React 16.3 引入了新的生命周期方法,同时标记了一些旧方法为过时。新的生命周期更加关注错误处理和异步渲染。
挂载阶段
constructor(props)
- 作用和使用场景与之前版本相同
static getDerivedStateFromProps(props, state)
- 作用:根据新的 props 计算并返回新的 state
- 使用场景:当 props 变化时需要更新 state 的场景
- 返回值:返回一个对象表示新的 state,返回 null 表示不需要更新
- 注意:这是一个静态方法,不能访问 this
render()
- 作用和使用场景与之前版本相同
componentDidMount()
- 作用和使用场景与之前版本相同
更新阶段
static getDerivedStateFromProps(props, state)
- 作用和使用场景与挂载阶段相同
shouldComponentUpdate(nextProps, nextState)
- 作用和使用场景与之前版本相同
getSnapshotBeforeUpdate(prevProps, prevState)
- 作用:在 DOM 更新前获取快照
- 使用场景:保存 DOM 状态,如滚动位置等
- 返回值:返回一个值,该值会作为 componentDidUpdate 的第三个参数
render()
- 作用和使用场景与之前版本相同
componentDidUpdate(prevProps, prevState, snapshot)
- 作用:组件更新后调用
- 使用场景:执行 DOM 操作、比较前后 props 或 state 的变化、发起 API 请求等
- 参数:snapshot 是 getSnapshotBeforeUpdate 返回的值
卸载阶段
- componentWillUnmount()
- 作用和使用场景与之前版本相同
错误处理
static getDerivedStateFromError(error)
- 作用:捕获子组件的错误并更新 state
- 使用场景:处理子组件错误,显示错误边界
- 返回值:返回一个对象表示新的 state
componentDidCatch(error, errorInfo)
- 作用:捕获子组件的错误并进行处理
- 使用场景:记录错误信息,显示错误 UI
- 参数:error 是错误对象,errorInfo 包含错误发生的组件栈信息
React 18 的生命周期变化
React 18 引入了并发渲染,对生命周期方法产生了一些影响。主要变化包括:
自动批处理
React 18 在更多情况下会自动批处理状态更新,包括异步事件处理函数中。这意味着多个 setState 调用可能会被合并为一次渲染,减少不必要的渲染次数。
新的根 API
React 18 引入了新的根 API createRoot,替代了旧的 render 方法。新的 API 支持并发渲染特性。
生命周期方法的行为变化
在 React 18 的并发模式下,以下生命周期方法可能会被多次调用:
rendergetDerivedStateFromPropsshouldComponentUpdategetSnapshotBeforeUpdate
这是因为 React 可能会在提交之前多次渲染组件,以实现更平滑的用户体验。因此,这些方法应该是纯函数,不应该有副作用。
函数组件的生命周期
随着 React Hooks 的引入,函数组件也可以实现类似类组件的生命周期功能。
useEffect Hook
useEffect 是最常用的 Hook,用于处理副作用,相当于类组件的 componentDidMount、componentDidUpdate 和 componentWillUnmount 的组合。
javascript
useEffect(() => {
// 组件挂载或更新后执行
return () => {
// 组件卸载或依赖项变化前执行
}
}, [dependencies])useLayoutEffect Hook
useLayoutEffect 与 useEffect 类似,但它在 DOM 更新后同步执行,而不是异步执行。适用于需要立即操作 DOM 的场景。
useMemo 和 useCallback Hook
useMemo 和 useCallback 用于性能优化,相当于类组件的 shouldComponentUpdate。
生命周期方法的使用场景总结
初始化和清理
- 初始化:在
constructor中设置初始 state,在componentDidMount中执行 DOM 操作、发起 API 请求、订阅事件等 - 清理:在
componentWillUnmount中清理定时器、取消订阅、清理事件监听器等
状态更新
- 根据 props 更新 state:使用
getDerivedStateFromProps - 根据 state 变化更新:使用
componentDidUpdate
性能优化
- 避免不必要的渲染:使用
shouldComponentUpdate或 React.memo - 缓存计算结果:使用
useMemo或useCallback
错误处理
- 捕获子组件错误:使用
getDerivedStateFromError和componentDidCatch
示例:类组件生命周期
javascript
class ExampleComponent extends React.Component {
constructor(props) {
super(props)
this.state = { count: 0 }
console.log('Constructor')
}
static getDerivedStateFromProps(props, state) {
console.log('getDerivedStateFromProps')
return null
}
componentDidMount() {
console.log('componentDidMount')
// 发起 API 请求、订阅事件等
}
shouldComponentUpdate(nextProps, nextState) {
console.log('shouldComponentUpdate')
return true
}
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log('getSnapshotBeforeUpdate')
return null
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('componentDidUpdate')
// 执行 DOM 操作、比较前后 props 或 state 的变化等
}
componentWillUnmount() {
console.log('componentWillUnmount')
// 清理定时器、取消订阅等
}
render() {
console.log('render')
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Increment
</button>
</div>
)
}
}示例:函数组件生命周期(使用 Hooks)
javascript
import React, {
useState,
useEffect,
useLayoutEffect,
useMemo,
useCallback
} from 'react'
function ExampleFunctionComponent({ prop }) {
const [count, setCount] = useState(0)
// 相当于 componentDidMount 和 componentDidUpdate
useEffect(() => {
console.log('useEffect')
// 发起 API 请求、订阅事件等
return () => {
// 清理定时器、取消订阅等
console.log('useEffect cleanup')
}
}, [prop]) // 依赖项数组
// 相当于 componentDidMount 和 componentDidUpdate,但同步执行
useLayoutEffect(() => {
console.log('useLayoutEffect')
return () => {
console.log('useLayoutEffect cleanup')
}
}, [prop])
// 缓存计算结果
const memoizedValue = useMemo(() => {
return count * 2
}, [count])
// 缓存回调函数
const memoizedCallback = useCallback(() => {
console.log('Memoized callback')
}, [count])
return (
<div>
<p>Count: {count}</p>
<p>Memoized value: {memoizedValue}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={memoizedCallback}>Memoized Callback</button>
</div>
)
}总结
React 的生命周期方法随着版本的更新而不断演进,从最初的三个阶段到引入错误处理和并发渲染,React 团队一直在优化组件的生命周期,以提供更好的开发体验和性能。
在使用生命周期方法时,应注意:
- 遵循 React 的最佳实践,使用适合当前版本的生命周期方法
- 避免在生命周期方法中执行耗时操作,以免影响性能
- 正确处理副作用,确保在组件卸载时清理所有资源
- 利用 React Hooks 简化函数组件的生命周期管理
通过合理使用生命周期方法,可以构建出更加健壮、高效的 React 应用。