Skip to content

React 生命周期

本文详细介绍React组件的生命周期方法,包括挂载、更新和卸载阶段的各个钩子函数及其使用场景。

生命周期概述

React组件的生命周期可以分为三个主要阶段:

  1. 挂载阶段(Mounting):组件被创建并插入到DOM中
  2. 更新阶段(Updating):组件重新渲染,更新DOM
  3. 卸载阶段(Unmounting):组件从DOM中移除

此外,React 16.3引入了一个新的阶段:

  1. 错误处理(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)

建议使用新的生命周期方法代替:

  • 使用constructorcomponentDidMount代替componentWillMount
  • 使用static getDerivedStateFromProps代替componentWillReceiveProps
  • 使用componentDidUpdate代替componentWillUpdate,如需在DOM更新前获取信息,使用getSnapshotBeforeUpdate

函数组件与Hooks

随着React Hooks的引入,函数组件现在可以使用类似生命周期的功能:

  • useState - 添加状态
  • useEffect - 处理副作用,替代多个生命周期方法
  • useContext - 消费上下文
  • useReducer - 复杂状态管理
  • useCallback - 记忆化回调函数
  • useMemo - 记忆化计算结果
  • useRef - 保存可变值和访问DOM
  • useLayoutEffect - 同步执行副作用
  • useImperativeHandle - 自定义暴露给父组件的实例值
  • useDebugValue - 在开发工具中显示自定义hook标签

类组件生命周期与Hooks对应关系

类组件生命周期Hooks等价实现
constructoruseState, useRef
getDerivedStateFromPropsuseState + useEffect
shouldComponentUpdateReact.memo + useMemo
render函数组件本身
componentDidMountuseEffect(() => {}, [])
componentDidUpdateuseEffect(() => {}, [dependencies])
componentWillUnmountuseEffect(() => { 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>
  );
}

最佳实践

生命周期方法使用建议

  1. 保持生命周期方法纯净

    • 避免在render中引入副作用
    • 避免在constructor中执行异步操作
  2. 合理使用shouldComponentUpdate

    • 仅用于性能优化
    • 考虑使用React.PureComponentReact.memo
  3. 在正确的生命周期中执行操作

    • 网络请求:componentDidMountcomponentDidUpdate
    • DOM操作:componentDidMountcomponentDidUpdategetSnapshotBeforeUpdate
    • 订阅设置:componentDidMount
    • 清理操作:componentWillUnmount
  4. 使用错误边界捕获错误

    • 将错误边界放在合适的层级
    • 提供有意义的错误UI
  5. 迁移到新的生命周期方法

    • 避免使用带有UNSAFE_前缀的方法
    • 使用静态getDerivedStateFromProps代替componentWillReceiveProps
    • 使用getSnapshotBeforeUpdate代替componentWillUpdate
  6. 考虑使用Hooks

    • 对于新组件,考虑使用函数组件+Hooks
    • 将复杂逻辑提取到自定义Hooks中