Skip to content

> 说明 > > 本文按 React 18/19 的常见面试口径整理。这里说的“生命周期”主要指 class 组件生命周期 API;函数组件没有这套类方法,通常用 useEffect / useLayoutEffect 等方式表达挂载、更新、卸载期间的副作用。

1. React 的生命周期有哪些?

React 通常将组件生命周期分为三个阶段:

  • 装载阶段(Mount),组件第一次在DOM树中被渲染的过程;
  • 更新过程(Update),组件状态发生变化,重新更新渲染的过程;
  • 卸载过程(Unmount),组件从DOM树中被移除的过程;

image.png

1)组件挂载阶段

挂载阶段组件被创建,然后组件实例插入到 DOM 中,完成组件的第一次渲染,该过程只会发生一次,在此阶段会依次调用以下这些方法:

  • constructor
  • getDerivedStateFromProps
  • render
  • componentDidMount

(1)constructor

组件的构造函数,第一个被执行,若没有显式定义它,会有一个默认的构造函数,但是若显式定义了构造函数,我们必须在构造函数中执行 super(props),否则无法在构造函数中拿到this。

如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数Constructor

constructor中通常只做两件事:

  • 初始化组件的 state
  • 给事件处理方法绑定 this
js
constructor(props) {
  super(props);
  // 不要在构造函数中调用 setState,可以直接给 state 设置初始值
  this.state = { counter: 0 }
  this.handleClick = this.handleClick.bind(this)
}

(2)getDerivedStateFromProps

js
static getDerivedStateFromProps(props, state)

这是个静态方法,所以不能在这个函数里使用 this,有两个参数 propsstate,分别指接收到的新参数和当前组件的 state 对象,这个函数会返回一个对象用来更新当前的 state 对象,如果不需要更新可以返回 null

它会在挂载和更新阶段被调用。但需要强调的是:getDerivedStateFromProps 不是日常首选方案,只有在少量“确实需要从 props 派生 state”的场景下才应该使用。

js
// 当 props.counter 变化时,赋值给 state 
class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      counter: 0
    }
  }
  static getDerivedStateFromProps(props, state) {
    if (props.counter !== state.counter) {
      return {
        counter: props.counter
      }
    }
    return null
  }
  
  handleClick = () => {
    this.setState({
      counter: this.state.counter + 1
    })
  }
  render() {
    return (
      <div>
        <h1 onClick={this.handleClick}>Hello, world!{this.state.counter}</h1>
      </div>
    )
  }
}

现在可以显式传入 counter ,但是这里有个问题,如果想要通过点击实现 state.counter 的增加,但这时会发现值不会发生任何变化,一直保持 props 传进来的值。这是由于在 React 16.4^ 的版本中 setStateforceUpdate 也会触发这个生命周期,所以当组件内部 state 变化后,就会重新走这个方法,同时会把 state 值赋值为 props 的值。因此需要多加一个字段来记录之前的 props 值,这样就会解决上述问题。具体如下:

js
// 这里只列出需要变化的地方
class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      // 增加一个 preCounter 来记录之前的 props 传来的值
      preCounter: 0,
      counter: 0
    }
  }
  static getDerivedStateFromProps(props, state) {
    // 跟 state.preCounter 进行比较
    if (props.counter !== state.preCounter) {
      return {
        counter: props.counter,
        preCounter: props.counter
      }
    }
    return null
  }
  handleClick = () => {
    this.setState({
      counter: this.state.counter + 1
    })
  }
  render() {
    return (
      <div>
        <h1 onClick={this.handleClick}>Hello, world!{this.state.counter}</h1>
      </div>
    )
  }
}

(3)render

render是React 中最核心的方法,一个组件中必须要有这个方法,它会根据状态 state 和属性 props 渲染组件。这个函数只做一件事,就是返回需要渲染的内容,所以不要在这个函数内做其他业务逻辑,通常调用该方法会返回以下类型中一个:

  • React 元素:这里包括原生的 DOM 以及 React 组件;
  • 数组和 Fragment(片段):可以返回多个元素;
  • Portals(插槽):可以将子元素渲染到不同的 DOM 子树种;
  • 字符串和数字:被渲染成 DOM 中的 text 节点;
  • 布尔值或 null:不渲染任何内容。

(4)componentDidMount()

componentDidMount() 会在组件挂载后(插入 DOM 树中)立即调用。该阶段通常进行以下操作:

  • 执行依赖于 DOM 的操作;
  • class 组件中发起数据请求;
  • 添加订阅消息(并在 componentWillUnmount 中清理);

如果在 componentDidMount 中调用 setState,会触发一次额外渲染。通常应优先在初始 state 中完成初始化;只有在“必须先拿到 DOM 再计算”的场景下,这种写法才更合理。

另外需要注意:

  • Strict Mode 开发环境下,React 可能会执行一次额外的挂载检查,因此你可能看到 componentDidMount -> componentWillUnmount -> componentDidMount 的开发期行为;
  • 这不代表生产环境会真的挂载两次,而是在帮助你发现缺失的清理逻辑。

在组件装载之后,将计数数字变为1:

js
class App extends React.Component  {
  constructor(props) {
    super(props)
    this.state = {
      counter: 0
    }
  }
  componentDidMount () {
    this.setState({
      counter: 1
    })
  }
  render ()  {
    return (
      <div className="counter">
        counter值: { this.state.counter }
      </div>
    )
  }
}

2)组件更新阶段

当组件的 props 改变了,或组件内部调用了 setState/forceUpdate,会触发更新重新渲染,这个过程可能会发生多次。这个阶段会依次调用下面这些方法:

  • getDerivedStateFromProps
  • shouldComponentUpdate
  • render
  • getSnapshotBeforeUpdate
  • componentDidUpdate

(1)shouldComponentUpdate

js
shouldComponentUpdate(nextProps, nextState)

在说这个生命周期函数之前,来看两个问题:

  • setState 函数在任何情况下都会导致组件重新渲染吗?例如下面这种情况:
js
this.setState({number: this.state.number})
  • 如果没有调用 setState,props 值也没有变化,是不是组件就不会重新渲染?

第一个问题答案是 ,第二个问题如果是父组件重新渲染时,不管传入的 props 有没有变化,都会引起子组件的重新渲染。

那么有没有什么方法解决在这两个场景下不让组件重新渲染进而提升性能呢?这个时候 shouldComponentUpdate 登场了,这个生命周期函数是用来提升速度的,它是在重新渲染组件开始前触发的,默认返回 true,可以比较 this.propsnextPropsthis.statenextState 值是否变化,来确认返回 true 或者 false。当返回 false 时,组件的更新过程停止,后续的 rendercomponentDidUpdate 也不会被调用。

注意: 添加 shouldComponentUpdate 方法时,不建议使用深度相等检查(如使用 JSON.stringify()),因为深比较效率很低,可能会比重新渲染组件效率还低。而且该方法维护比较困难,建议使用该方法会产生明显的性能提升时使用。

(2)getSnapshotBeforeUpdate

js
getSnapshotBeforeUpdate(prevProps, prevState)

这个方法在 render 之后,componentDidUpdate 之前调用,有两个参数 prevPropsprevState,表示更新之前的 propsstate,这个函数必须要和 componentDidUpdate 一起使用,并且要有一个返回值,默认是 null,这个返回值作为第三个参数传给 componentDidUpdate

(3)componentDidUpdate

componentDidUpdate() 会在更新后会被立即调用,首次渲染不会执行此方法。 该阶段通常进行以下操作:

  • 当组件更新后,对 DOM 进行操作;
  • 如果你对更新前后的 props 进行了比较,也可以选择在此处进行网络请求;(例如,当 props 未发生变化时,则不会执行网络请求)。
js
componentDidUpdate(prevProps, prevState, snapshot){}

该方法有三个参数:

  • prevProps: 更新前的props
  • prevState: 更新前的state
  • snapshot: getSnapshotBeforeUpdate()生命周期的返回值

3)组件卸载阶段

卸载阶段只有一个生命周期函数,componentWillUnmount() 会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作:

  • 清除 timer,取消网络请求或清除
  • 取消在 componentDidMount() 中创建的订阅等;

这个生命周期在一个组件被卸载和销毁之前被调用,因此你不应该再这个方法中使用 setState,因为组件一旦被卸载,就不会再装载,也就不会重新渲染。

4)错误处理阶段

错误边界(Error Boundary)通常由两个 API 组合完成:

  • static getDerivedStateFromError(error):根据错误更新 UI 状态,比如切换到兜底页面;
  • componentDidCatch(error, info):记录错误日志、上报监控。

其中 componentDidCatch(error, info) 会接收两个参数:

  • error:抛出的错误;
  • info:包含 componentStack 的附加信息。

当前 class 生命周期可以这样理解

React 常见生命周期如下: image.png

更稳妥的总览方式是:

  • 挂载阶段constructor -> getDerivedStateFromProps -> render -> componentDidMount
  • 更新阶段getDerivedStateFromProps -> shouldComponentUpdate -> render -> getSnapshotBeforeUpdate -> componentDidUpdate
  • 卸载阶段componentWillUnmount
  • 错误处理getDerivedStateFromError / componentDidCatch

补充两点:

  • render 是唯一必须实现的方法,其他生命周期都按需使用。
  • getDefaultPropsgetInitialStatecomponentWillMount 这些都属于旧时代 API,不应再作为今天的“主要生命周期”来记忆。

2. React 废弃了哪些生命周期?为什么?

现代 React 里最需要记住的,是这 3 个旧生命周期已经不应继续当成常规方案使用:

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

在迁移期里你可能还会看到它们的 UNSAFE_ 版本:

  • UNSAFE_componentWillMount
  • UNSAFE_componentWillReceiveProps
  • UNSAFE_componentWillUpdate

为什么它们会被弃用

核心原因不是“语法不好看”,而是它们都发生在 render 之前,在现代 React 的调度模型下,这些阶段更容易出现:

  • 被重复调用;
  • 执行了不该放在 render 前的副作用;
  • stateprops 关系变得混乱。

更稳妥的替代关系

旧生命周期今天更常见的替代方式
componentWillMount初始化放 constructor / class fields;副作用放 componentDidMountuseEffect
componentWillReceiveProps直接根据 props 渲染;副作用用 componentDidUpdate / useEffect;极少数派生 state 用 getDerivedStateFromProps
componentWillUpdate更新前读取 DOM 用 getSnapshotBeforeUpdate;副作用放 componentDidUpdate

这题怎么答更现代

可以直接说:

  1. 废弃的是 3 个 componentWill* 系列旧生命周期。
  2. 它们的问题主要是 render 前副作用和现代调度下的不可预测性。
  3. React 保留了 UNSAFE_ 前缀版本给旧项目迁移。
  4. 新代码应优先使用 componentDidMountcomponentDidUpdategetSnapshotBeforeUpdategetDerivedStateFromProps,或函数组件里的 Hooks。

3. props 改变后,应该在哪个生命周期里处理?

这道题不建议再答成“统一放在 getDerivedStateFromProps”。更准确的答案应该分场景:

1. 大多数情况:什么都不用做

只要组件根据 props 渲染,父组件传来新 props,子组件自然会重新 render。

2. props 变化后要做副作用

比如请求数据、上报埋点、重置订阅:

  • class 组件:componentDidUpdate
  • 函数组件:useEffect

3. 确实需要从 props 派生 state

只有这种少见场景,才考虑 getDerivedStateFromProps

js
static getDerivedStateFromProps(nextProps, prevState) {
  if (nextProps.type !== prevState.type) {
    return { type: nextProps.type };
  }
  return null;
}

4. 面试里一句话总结

  • 直接渲染优先
  • 副作用放更新后阶段
  • 只有极少数场景才用 getDerivedStateFromProps

4. React 性能优化在哪个生命周期?它优化的原理是什么?

如果题目问“生命周期里怎么做性能优化”,最直接相关的 class 组件 API 确实是 shouldComponentUpdate。但要注意,现代 React 的性能优化并不只靠某一个生命周期

1. class 组件里最直接的生命周期优化点

shouldComponentUpdate(nextProps, nextState) 可以决定这次更新是否继续往下执行。

js
shouldComponentUpdate(nextProps) {
  return this.props.num !== nextProps.num;
}

当它返回 false 时,这次更新会被跳过,后续的 rendercomponentDidUpdate 也不会执行。

2. 它的原理是什么

本质上是在做一件事:

> 如果输入没有变化,就跳过这次不必要的渲染。

也就是说,性能优化并不是“让 React 更快渲染”,而是避免本来就不该发生的渲染

3. 现代 React 更常见的优化方式

  • class 组件:PureComponent、合理使用 shouldComponentUpdate
  • 函数组件:React.memo
  • 数据层面:保持不可变更新,确保浅比较有效
  • 结构层面:拆分组件、缩小状态影响范围

4. 这题里要避免的误区

  • 不要把 shouldComponentUpdate 当成默认必写项;
  • 不要为了比较对象而做昂贵的深比较;
  • 不要用 JSON.stringify 这类方式当通用优化方案;
  • 真正关键的是让数据更新保持引用稳定、边界清晰、不可变

5. state 和 props 触发更新的生命周期分别有什么区别?

现代 React 里,这两种更新大部分生命周期是共用的,并没有一套完全不同的流程。

1. 共同点

无论是:

  • state 变化
  • props 变化

都会进入同一套更新流程:

  • getDerivedStateFromProps
  • shouldComponentUpdate
  • render
  • getSnapshotBeforeUpdate
  • componentDidUpdate

2. 真正的区别在哪

区别不在“会走哪些生命周期”,而在于变化的来源不同

  • state 更新来源于组件内部,比如 setState
  • props 更新来源于父组件重新渲染并传入新值

3. 一个旧口径需要去掉

以前常说“props 更新比 state 更新多一个 componentWillReceiveProps”,这已经不适合作为今天的标准答案,因为:

  • componentWillReceiveProps 已经是旧生命周期;
  • 现代 React 更推荐直接 render、componentDidUpdateuseEffect 或少量 getDerivedStateFromProps

4. 面试里可以这么答

  • stateprops 更新在现代 class 组件里走的大部分生命周期是一样的;
  • 差别主要在于更新来源;
  • 如果要区分处理,通常在 componentDidUpdate(prevProps, prevState) 里比较前后值。

6. React 中发起网络请求应该在哪个生命周期中进行?为什么?

如果题目限定的是 class 组件,传统回答是:

  • 首次请求放在 componentDidMount
  • 依赖 props / state 变化重新请求放在 componentDidUpdate,并记得做条件判断

原因很简单:

  • 这两个阶段都属于 commit 之后,适合放副作用;
  • 不应该把网络请求放到 render 或已废弃的 componentWillMount 里。

如果是 函数组件,对应的思路就是 useEffect

需要再补一个现代视角:

  • 在 React 官方文档里,直接在组件生命周期里手写请求虽然可行,但并不是数据获取体验最好的方案;
  • 在实际项目里,通常还会结合框架的数据层、路由 loader、React Query / SWR 等方案处理缓存、并发、去重和 loading 状态。

这题里最该删除的旧说法

  • 不要再说“同步状态更新可以放到 componentWillMount”;
  • 不要再把 componentWillMount 当成请求前置优化手段;
  • 它已经是旧生命周期,也不适合承载副作用。

7. React 16+ 引入的新生命周期有哪些?

React 16 之后,class 生命周期最值得记住的新 API 主要有 3 个:

  • getDerivedStateFromProps
  • getSnapshotBeforeUpdate
  • getDerivedStateFromError

它们分别解决的是:

  • 派生 state
  • 更新前读取 DOM 快照
  • 错误边界兜底

也可以从阶段来理解

关于 React 16 开始应用的新生命周期:

  • Render 阶段:用于计算 UI,可能会被 React 打断或重试,所以不应放副作用。
  • Pre-commit 阶段:真实 DOM 提交前的最后时机,典型 API 是 getSnapshotBeforeUpdate
  • Commit 阶段:真实 DOM 已提交,可以安全执行副作用,典型 API 是 componentDidMountcomponentDidUpdatecomponentDidCatch

当前 class 生命周期流程

  • 挂载:
    • constructor
    • getDerivedStateFromProps
    • render
    • componentDidMount
  • 更新:
    • getDerivedStateFromProps
    • shouldComponentUpdate
    • render
    • getSnapshotBeforeUpdate
    • componentDidUpdate
  • 卸载:
    • componentWillUnmount
  • 错误处理:
    • getDerivedStateFromError
    • componentDidCatch

评论区

欢迎留言、补充或勘误。

xiaoba.blog