返回首页

说说 React 生命周期有哪些不同阶段?每个阶段对应的方法是?

问题解析(面试官考察点)

面试官通过此问题主要考察:

  • 对 React 组件生命周期的全面理解
  • 各生命周期方法的执行时机和用途
  • React 16.3+ 新版本生命周期的掌握
  • 能否正确使用生命周期解决实际问题

核心概念(基础知识点)

什么是生命周期

生命周期指组件从创建到销毁的整个过程,React 在特定时机调用特定方法,让开发者可以介入处理。

三个阶段

  1. 创建阶段(Mounting):组件被创建并插入 DOM
  2. 更新阶段(Updating):组件重新渲染
  3. 卸载阶段(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();
  };
}, []);

面试要点

  1. 三个阶段

    • 创建:constructor → getDerivedStateFromProps → render → componentDidMount
    • 更新:getDerivedStateFromProps → shouldComponentUpdate → render → getSnapshotBeforeUpdate → componentDidUpdate
    • 卸载:componentWillUnmount
  2. 新版本变化

    • 废弃:componentWillMount、componentWillReceiveProps、componentWillUpdate
    • 新增:getDerivedStateFromProps、getSnapshotBeforeUpdate
    • 原因:异步渲染安全性
  3. 常用生命周期用途

    • constructor:初始化 state
    • componentDidMount:数据获取、订阅
    • componentDidUpdate:根据 props 变化执行操作
    • componentWillUnmount:清理工作
  4. Hooks 对应关系

    • useEffect 替代 componentDidMount、componentDidUpdate、componentWillUnmount
    • useLayoutEffect 替代 componentDidMount、componentDidUpdate(同步执行)
  5. 常见面试题

    • React 生命周期有哪些阶段?
    • 为什么废弃 componentWillMount?
    • getDerivedStateFromProps 的作用?
    • 在哪些生命周期可以调用 setState?
    • 函数组件如何实现生命周期?
  6. 注意事项

    • constructor 和 render 中不要调用 setState
    • shouldComponentUpdate 不要进行深层比较
    • componentWillUnmount 必须清理副作用