Skip to content

React 生命周期详解

React 组件的生命周期是指组件从创建到销毁的整个过程。不同版本的 React 对生命周期方法有不同的定义和调整。本文将详细介绍 React 各个版本的生命周期方法、作用及使用场景。

React 16.3 之前的生命周期

在 React 16.3 之前,生命周期可以分为三个阶段:挂载(Mounting)、更新(Updating)和卸载(Unmounting)。

挂载阶段

  1. constructor(props)

    • 作用:初始化组件的状态和绑定方法
    • 使用场景:设置初始 state,绑定事件处理函数
    • 注意:在构造函数中应避免调用 setState(),因为此时组件还未挂载
  2. componentWillMount()

    • 作用:组件即将挂载到 DOM 前调用
    • 使用场景:进行一些初始化操作,但不推荐在其中进行 API 调用
    • 注意:在 React 16.3 中被标记为过时,在 React 17 中被移除
  3. render()

    • 作用:渲染组件的 UI
    • 使用场景:返回 JSX 或 null
    • 注意:render 方法应该是纯函数,不应该修改组件状态
  4. componentDidMount()

    • 作用:组件挂载到 DOM 后调用
    • 使用场景:执行 DOM 操作、发起 API 请求、订阅事件等

更新阶段

  1. componentWillReceiveProps(nextProps)

    • 作用:当组件接收新的 props 时调用
    • 使用场景:根据新 props 更新 state
    • 注意:在 React 16.3 中被标记为过时,在 React 17 中被移除
  2. shouldComponentUpdate(nextProps, nextState)

    • 作用:决定组件是否需要重新渲染
    • 使用场景:性能优化,避免不必要的渲染
    • 返回值:布尔值,true 表示需要更新,false 表示不需要
  3. componentWillUpdate(nextProps, nextState)

    • 作用:组件即将更新前调用
    • 使用场景:准备更新所需的数据
    • 注意:在 React 16.3 中被标记为过时,在 React 17 中被移除
  4. render()

    • 作用:重新渲染组件的 UI
  5. componentDidUpdate(prevProps, prevState)

    • 作用:组件更新后调用
    • 使用场景:执行 DOM 操作、比较前后 props 或 state 的变化、发起 API 请求等

卸载阶段

  1. componentWillUnmount()
    • 作用:组件即将从 DOM 中卸载前调用
    • 使用场景:清理定时器、取消订阅、清理事件监听器等

React 16.3 及之后的生命周期

React 16.3 引入了新的生命周期方法,同时标记了一些旧方法为过时。新的生命周期更加关注错误处理和异步渲染。

挂载阶段

  1. constructor(props)

    • 作用和使用场景与之前版本相同
  2. static getDerivedStateFromProps(props, state)

    • 作用:根据新的 props 计算并返回新的 state
    • 使用场景:当 props 变化时需要更新 state 的场景
    • 返回值:返回一个对象表示新的 state,返回 null 表示不需要更新
    • 注意:这是一个静态方法,不能访问 this
  3. render()

    • 作用和使用场景与之前版本相同
  4. componentDidMount()

    • 作用和使用场景与之前版本相同

更新阶段

  1. static getDerivedStateFromProps(props, state)

    • 作用和使用场景与挂载阶段相同
  2. shouldComponentUpdate(nextProps, nextState)

    • 作用和使用场景与之前版本相同
  3. getSnapshotBeforeUpdate(prevProps, prevState)

    • 作用:在 DOM 更新前获取快照
    • 使用场景:保存 DOM 状态,如滚动位置等
    • 返回值:返回一个值,该值会作为 componentDidUpdate 的第三个参数
  4. render()

    • 作用和使用场景与之前版本相同
  5. componentDidUpdate(prevProps, prevState, snapshot)

    • 作用:组件更新后调用
    • 使用场景:执行 DOM 操作、比较前后 props 或 state 的变化、发起 API 请求等
    • 参数:snapshot 是 getSnapshotBeforeUpdate 返回的值

卸载阶段

  1. componentWillUnmount()
    • 作用和使用场景与之前版本相同

错误处理

  1. static getDerivedStateFromError(error)

    • 作用:捕获子组件的错误并更新 state
    • 使用场景:处理子组件错误,显示错误边界
    • 返回值:返回一个对象表示新的 state
  2. componentDidCatch(error, errorInfo)

    • 作用:捕获子组件的错误并进行处理
    • 使用场景:记录错误信息,显示错误 UI
    • 参数:error 是错误对象,errorInfo 包含错误发生的组件栈信息

React 18 的生命周期变化

React 18 引入了并发渲染,对生命周期方法产生了一些影响。主要变化包括:

自动批处理

React 18 在更多情况下会自动批处理状态更新,包括异步事件处理函数中。这意味着多个 setState 调用可能会被合并为一次渲染,减少不必要的渲染次数。

新的根 API

React 18 引入了新的根 API createRoot,替代了旧的 render 方法。新的 API 支持并发渲染特性。

生命周期方法的行为变化

在 React 18 的并发模式下,以下生命周期方法可能会被多次调用:

  • render
  • getDerivedStateFromProps
  • shouldComponentUpdate
  • getSnapshotBeforeUpdate

这是因为 React 可能会在提交之前多次渲染组件,以实现更平滑的用户体验。因此,这些方法应该是纯函数,不应该有副作用。

函数组件的生命周期

随着 React Hooks 的引入,函数组件也可以实现类似类组件的生命周期功能。

useEffect Hook

useEffect 是最常用的 Hook,用于处理副作用,相当于类组件的 componentDidMountcomponentDidUpdatecomponentWillUnmount 的组合。

javascript
useEffect(() => {
  // 组件挂载或更新后执行
  return () => {
    // 组件卸载或依赖项变化前执行
  }
}, [dependencies])

useLayoutEffect Hook

useLayoutEffectuseEffect 类似,但它在 DOM 更新后同步执行,而不是异步执行。适用于需要立即操作 DOM 的场景。

useMemo 和 useCallback Hook

useMemouseCallback 用于性能优化,相当于类组件的 shouldComponentUpdate

生命周期方法的使用场景总结

初始化和清理

  • 初始化:在 constructor 中设置初始 state,在 componentDidMount 中执行 DOM 操作、发起 API 请求、订阅事件等
  • 清理:在 componentWillUnmount 中清理定时器、取消订阅、清理事件监听器等

状态更新

  • 根据 props 更新 state:使用 getDerivedStateFromProps
  • 根据 state 变化更新:使用 componentDidUpdate

性能优化

  • 避免不必要的渲染:使用 shouldComponentUpdate 或 React.memo
  • 缓存计算结果:使用 useMemouseCallback

错误处理

  • 捕获子组件错误:使用 getDerivedStateFromErrorcomponentDidCatch

示例:类组件生命周期

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 团队一直在优化组件的生命周期,以提供更好的开发体验和性能。

在使用生命周期方法时,应注意:

  1. 遵循 React 的最佳实践,使用适合当前版本的生命周期方法
  2. 避免在生命周期方法中执行耗时操作,以免影响性能
  3. 正确处理副作用,确保在组件卸载时清理所有资源
  4. 利用 React Hooks 简化函数组件的生命周期管理

通过合理使用生命周期方法,可以构建出更加健壮、高效的 React 应用。

基于 VitePress 的本地知识库