在React中的生命周期方法有哪些,在一个组件加载过程中这些生命周期都干了些什么,在项目中你常用到的生命周期方法有哪些?

大白话 在React中的生命周期方法有哪些,在一个组件加载过程中这些生命周期都干了些什么,在项目中你常用到的生命周期方法有哪些?

引言

咱前端工程师谁还没在面试时被React生命周期问懵过啊?明明项目里天天用组件,可一被追问“组件从生到死都经历了哪些步骤”“某个方法在啥时候执行”,就跟玩游戏不知道关卡流程一样,脑子瞬间卡壳。就像你煮面条,知道要加水、下面、加盐,但问你水开的温度、煮面的时长这些细节,可能就含糊了。

其实啊,React生命周期没那么玄乎,它就像组件的“一生”,从出生、成长到死亡,每个阶段都有对应的“任务”。掌握了它,写组件时能少踩很多坑,性能优化也能找对方向。这篇文章就像“布洛芬”,帮你缓解面对生命周期时的头大,用大白话把每个关卡讲明白,让你看完就能理清思路,面试不慌,项目顺手。

技术原理

什么是React生命周期

React生命周期就是组件从创建、渲染到更新、卸载的整个过程中,会自动触发的一系列方法的集合。你可以把它想象成一个人从出生、长大、变老到死亡的一生,每个阶段都有特定的事情要做,组件也一样,每个生命周期方法都有它该干的活儿。

生命周期的三个阶段及对应方法

挂载阶段(组件出生)

这个阶段组件从无到有,首次被渲染到DOM中,就像一个宝宝刚出生,要办理出生证、登记信息等。

  • constructor:组件的构造函数,在组件被创建时最先调用。就像给新生儿办出生证,主要用来初始化组件的state,以及给事件处理函数绑定this。不过要注意,如果你在里面用了super(props),才能在构造函数里访问this.props。
  • static getDerivedStateFromProps:这个方法比较特殊,它是静态方法,在组件挂载和更新时都会被调用。作用是根据props来更新state,返回一个对象来更新state,如果返回null就不更新。它就像一个“翻译官”,把props里的信息转换成组件内部的state。
  • render:这是一个必须有的方法,负责渲染组件的UI。它就像画家画画,根据state和props画出组件的样子。需要注意的是,render方法不能包含副作用操作,比如修改state、调用API等,它应该是纯函数,只根据输入返回对应的输出。
  • componentDidMount:组件挂载完成后调用,也就是组件已经被渲染到DOM中了。这时候可以进行一些副作用操作,比如调用API获取数据、设置定时器、绑定事件监听等。就像宝宝出生后,医生检查没问题,家长可以开始给宝宝喂奶、换尿布了。
更新阶段(组件成长)

当组件的props或者state发生变化时,就会进入更新阶段,组件会重新渲染,就像人在成长过程中,身高、体重等发生变化一样。

  • static getDerivedStateFromProps:和挂载阶段一样,在更新时也会被调用,还是根据props更新state。
  • shouldComponentUpdate:决定组件是否需要重新渲染,返回布尔值,true表示需要重新渲染,false表示不需要。它就像一个“门卫”,判断是不是有必要让组件更新,能起到优化性能的作用,避免不必要的渲染。比如当props或state的变化不会影响UI时,就可以返回false。
  • render:更新阶段也会调用render方法,重新渲染UI。
  • getSnapshotBeforeUpdate:在组件更新前获取DOM的快照,返回的值会作为参数传给componentDidUpdate。比如可以在这里获取滚动位置,在更新后恢复滚动位置。就像搬家前,先拍张照片记录家具的位置,搬完后根据照片摆放。
  • componentDidUpdate:组件更新完成后调用。可以在这里处理更新后的操作,比如根据新的props调用API,或者操作DOM。不过要注意,这里如果要修改state,一定要加判断条件,否则会导致无限循环更新。
卸载阶段(组件死亡)

当组件从DOM中被移除时,就进入了卸载阶段,就像人去世后要处理后事一样。

  • componentWillUnmount:在组件卸载和销毁前调用。主要用来清理一些副作用,比如清除定时器、取消事件监听、取消API请求等,防止内存泄漏。就像离开房间前要关灯、关空调一样,做好收尾工作。

代码示例

下面用一个简单的计数器组件来展示React生命周期的各个方法:

import React, { Component } from 'react';

class Counter extends Component {
  // 1. 构造函数,组件创建时最先调用
  constructor(props) {
    super(props); // 调用父类的构造函数,传入props
    // 初始化state,设置计数器初始值为0
    this.state = {
      count: 0,
      timer: null // 用来存储定时器ID
    };
    console.log('constructor:组件构造中,初始化state');
  }

  // 2. 静态方法,根据props更新state
  static getDerivedStateFromProps(nextProps, prevState) {
    console.log('getDerivedStateFromProps:根据props更新state');
    // 如果父组件传入了初始值,就用父组件的初始值更新state
    if (nextProps.initialCount !== prevState.count && prevState.count === 0) {
      return { count: nextProps.initialCount };
    }
    return null; // 不更新state
  }

  // 3. 处理点击增加计数的方法
  handleIncrement = () => {
    this.setState({ count: this.state.count + 1 });
  };

  // 4. 处理点击减少计数的方法
  handleDecrement = () => {
    this.setState({ count: this.state.count - 1 });
  };

  // 5. 决定组件是否需要更新
  shouldComponentUpdate(nextProps, nextState) {
    console.log('shouldComponentUpdate:判断是否需要更新');
    // 当计数是偶数时才更新,奇数时不更新,这里只是举例,实际根据需求判断
    return nextState.count % 2 === 0;
  }

  // 6. 获取更新前的DOM快照
  getSnapshotBeforeUpdate(prevProps, prevState) {
    console.log('getSnapshotBeforeUpdate:获取更新前的快照');
    // 这里可以获取一些DOM信息,比如滚动位置等,这里只是举例返回当前计数
    return { prevCount: prevState.count };
  }

  // 7. 组件挂载完成后调用
  componentDidMount() {
    console.log('componentDidMount:组件挂载完成');
    // 设置一个定时器,每5秒自动增加计数
    const timer = setInterval(() => {
      this.setState({ count: this.state.count + 1 });
    }, 5000);
    this.setState({ timer });
  }

  // 8. 组件更新完成后调用
  componentDidUpdate(prevProps, prevState, snapshot) {
    console.log('componentDidUpdate:组件更新完成');
    console.log('更新前的计数是:', snapshot.prevCount);
    // 如果计数达到10,就停止定时器
    if (this.state.count >= 10) {
      clearInterval(this.state.timer);
    }
  }

  // 9. 组件卸载前调用
  componentWillUnmount() {
    console.log('componentWillUnmount:组件即将卸载');
    // 清除定时器,防止内存泄漏
    clearInterval(this.state.timer);
  }

  // 10. 渲染组件UI
  render() {
    console.log('render:渲染组件');
    return (
      <div>
        <h2>计数器:{this.state.count}</h2>
        <button onClick={this.handleIncrement}>增加</button>
        <button onClick={this.handleDecrement}>减少</button>
      </div>
    );
  }
}

export default Counter;

对比效果

生命周期方法触发时机作用注意事项
constructor组件创建时,挂载阶段最先调用初始化state,绑定事件处理函数避免在这里调用setState,会导致额外渲染;如果不初始化state和绑定方法,可以不写
static getDerivedStateFromProps挂载和更新阶段,在render之前调用根据props更新state是静态方法,不能访问this;返回对象更新state,返回null不更新;容易导致代码复杂,谨慎使用
render挂载和更新阶段都会调用渲染组件UI是纯函数,不能包含副作用;必须返回JSX、null等
componentDidMount挂载阶段,组件被渲染到DOM后调用执行副作用操作,如调用API、设置定时器等可以在这里调用setState,会触发重新渲染,但要注意性能
shouldComponentUpdate更新阶段,在render之前调用判断组件是否需要重新渲染返回布尔值,true表示需要,false表示不需要;主要用于性能优化
getSnapshotBeforeUpdate更新阶段,在render之后,DOM更新之前调用获取DOM更新前的快照返回的值会传给componentDidUpdate;可以获取滚动位置等DOM信息
componentDidUpdate更新阶段,DOM更新后调用处理更新后的操作,如根据新数据调用API等可以在这里调用setState,但必须加判断条件,否则会无限循环;prevProps和prevState用于比较前后变化
componentWillUnmount卸载阶段,组件被移除前调用清理副作用,如清除定时器、取消事件监听等不能在这里调用setState,因为组件即将卸载,不会重新渲染

面试题的正常回答和大白话回答

正常回答

React的生命周期主要分为挂载、更新和卸载三个阶段。

挂载阶段:constructor用于初始化state和绑定方法;static getDerivedStateFromProps根据props更新state;render负责渲染UI;componentDidMount在组件挂载后执行副作用操作。

更新阶段:当props或state变化时触发,static getDerivedStateFromProps再次根据props更新state;shouldComponentUpdate判断是否需要更新;render重新渲染;getSnapshotBeforeUpdate获取DOM快照;componentDidUpdate处理更新后的操作。

卸载阶段:componentWillUnmount用于清理副作用。

大白话回答

React组件就像人一样有一生。出生(挂载)的时候,先办“出生证”(constructor)初始化信息,然后根据外部信息调整自己(getDerivedStateFromProps),接着“长”出样子(render),最后彻底“落地”了(componentDidMount)就可以干些正事,比如发请求。

长大(更新)的时候,先看看外部信息有没有变,要不要调整自己(getDerivedStateFromProps),再决定要不要“变样子”(shouldComponentUpdate),然后重新“长”样子(render),更新前先拍个“快照”(getSnapshotBeforeUpdate),更新完了再处理后续事(componentDidUpdate)。

死亡(卸载)的时候,就要把自己留下的“烂摊子”收拾好(componentWillUnmount),比如关掉定时器。

总结

React生命周期是组件从创建到销毁过程中一系列自动触发的方法,分为挂载、更新和卸载三个阶段。每个阶段的方法都有其特定的作用,constructor初始化基础信息,render负责展示外观,componentDidMount和componentDidUpdate处理各种外部交互,shouldComponentUpdate优化性能,componentWillUnmount做好收尾工作。

了解这些生命周期方法,能帮助我们更好地控制组件的行为,避免不必要的错误和性能问题。就像知道了游戏的关卡流程,才能更顺利地通关一样,掌握生命周期,写React组件也能更得心应手。

扩展思考及解答

1. React Hooks出现后,生命周期方法还有必要学吗?

有必要。虽然Hooks(如useState、useEffect)可以替代大部分生命周期方法,让代码更简洁,但Hooks的设计理念是建立在对生命周期的理解之上的。比如useEffect可以模拟componentDidMount、componentDidUpdate和componentWillUnmount的组合效果。

了解生命周期方法,能帮助我们更好地理解Hooks的工作原理,知道什么时候该用useEffect,以及如何正确设置依赖项。而且在维护一些旧项目时,可能还会遇到使用类组件和生命周期方法的代码,所以学习生命周期方法仍然很有必要。

2. 如何优化生命周期中的性能问题?

  • 合理使用shouldComponentUpdate:通过比较前后props和state的变化,决定是否需要重新渲染,避免不必要的渲染。
  • 避免在render方法中创建新函数或对象:因为每次render都会创建新的引用,可能导致子组件的shouldComponentUpdate判断失效,引发不必要的更新。
  • 使用React.memo:对函数组件进行包装,相当于给组件加了一个shouldComponentUpdate,浅比较props,相同则不重新渲染。
  • 减少componentDidUpdate中的不必要操作:在里面调用setState一定要加判断条件,避免无限循环;只在必要时执行耗时操作。
  • 合理使用getDerivedStateFromProps:不要过度使用,否则会让组件逻辑复杂,难以维护,它主要用于props决定state的场景。

3. 父子组件生命周期的执行顺序是怎样的?

挂载阶段:父组件的constructor → 父组件的getDerivedStateFromProps → 父组件的render → 子组件的constructor → 子组件的getDerivedStateFromProps → 子组件的render → 子组件的componentDidMount → 父组件的componentDidMount。

更新阶段:父组件的getDerivedStateFromProps → 父组件的shouldComponentUpdate → 父组件的render → 子组件的getDerivedStateFromProps → 子组件的shouldComponentUpdate → 子组件的render → 子组件的getSnapshotBeforeUpdate → 父组件的getSnapshotBeforeUpdate → 子组件的componentDidUpdate → 父组件的componentDidUpdate。

卸载阶段:父组件的componentWillUnmount → 子组件的componentWillUnmount。

简单来说,就是父组件先开始,但子组件先完成挂载和更新,卸载时父组件先准备卸载,子组件先完成卸载。

4. 错误边界与生命周期有什么关系?

错误边界是React中用于捕获子组件树中的JavaScript错误,并渲染备用UI的组件。它利用了两个生命周期方法:static getDerivedStateFromError和componentDidCatch。

static getDerivedStateFromError:当子组件抛出错误时调用,返回一个对象来更新state,用于渲染备用UI。

componentDidCatch:同样在子组件抛出错误时调用,用于记录错误信息,比如发送到日志服务。

错误边界不能捕获自身的错误,也不能捕获异步代码(如setTimeout)和事件处理函数中的错误,它主要针对渲染过程、生命周期方法和子组件的构造函数中的错误。通过这两个生命周期方法,错误边界实现了对错误的处理,防止整个应用崩溃。

结尾

看完这篇文章,是不是觉得React生命周期没那么难了?就像玩游戏摸清了关卡流程,心里踏实多了。这些生命周期方法就像组件的“说明书”,了解它们,你就能更好地驾驭组件,写出更稳定、性能更好的代码。

在实际项目中,根据需求灵活运用这些方法,比如在componentDidMount里发请求,在componentWillUnmount里清定时器,用shouldComponentUpdate优化性能。遇到问题时,想想组件处于哪个“人生阶段”,该用哪个方法解决,就像生病时知道该吃哪种药一样。

如果你在使用React生命周期时遇到过什么奇葩问题,或者有自己的小技巧,欢迎在评论区留言分享,咱们一起交流学习,让前端之路走得更顺!记住,掌握生命周期,就像给组件装上了“导航”,再也不怕迷路啦!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端布洛芬

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值