React类组件生命周期详解

105 阅读8分钟

React 类组件生命周期

React从v16.3的版本开始, 对⽣命周期的钩⼦进⾏了渐进式的调整,分别废弃和新增了⼀些⽣命周期 的钩⼦函数。

在 React 中,对于每⼀次由状态改变导致⻚⾯视图的改变,都会经历两个阶段: render 阶段 、 commit 阶段 。

只有 class 组件才有⽣命周期,因为 class 组件会创建对应的实例,⽽函数组件不会。组件实例从被创 建到被销毁的过程称为组件的⽣命周期。

由 class 组件创建的实例具有⽣命周期,它的 render 函数在 render 阶段执⾏,并在此阶段进⾏ DOM 节点的 diff(diff 算法就是在此阶段进⾏的),找出需要改变的 DOM 操作。然后在 commit 阶段将对 应的 DOM 操作提交⾄视图中。

⽽ class 组件实例的所有⽣命周期函数,都会在 render 阶段和 commit 阶段执⾏。

老版生命周期

1712934385720.png

挂载

  • constructor
  • componentWillMount
  • render
  • componentDidMount ⼀定⼦组件先挂载完成,才到⽗亲节点(⾯试考点)

更新

  • componentWillReceiveProps
  • shouldComponentUpdate
  • componentWillUpdate
  • render
  • componentDidUpdate

卸载

  • componentWillUnmount

新版生命周期

1712934533042.png

render 阶段会执行众多生命周期钩子,例如:

在首次渲染时执行

  1. constructor
  2. getDerivedStateFromProps(静态⽅法)
  3. componentWillMount
  4. render,

在更新时执行

  1. shouldComponentUpdate、
  2. render,
  3. getSnapshotBeforeUpdate
  4. componentDidUpdate

constructor该⽅法只会执⾏⼀次,调⽤该⽅法会返回⼀个组件实例。 在初始化阶段执⾏,可直接对 this.state 赋值。其他⽣命周期函数中只能通过 this.setState 修改 state,不能直接为 this.state 赋值。

使用场景:

⼀般在 constructor 中做⼀些组件的初始化⼯作,例如:初始化组件的 state。

Render阶段

componentWillReceiveProps

在已挂载组件接收到新的 props 之前调⽤。你可以在这个函数中⽐较新旧 props,并根据新旧 props 更改 state。但是它会破坏 props 数据的单⼀数据源。 在⾸次渲染组件时,不会调⽤此⽣命周期钩⼦;使⽤ this.setState 触发组件更新时,也不会调 ⽤此⽣命周期钩⼦。

不过要注意:如果是⽗组件渲染导致了组件的重新渲染,即使传给该组件的 props 没变,该组件中的 这个⽣命周期函数也会被调⽤。 我们⼀般不使⽤此⽣命周期函数,因为它通常会破坏数据源的单⼀性。

getDerivedStateFromProps

它是⼀个静态⽅法,接收 props 和 state 两个参数。它会在调⽤ render ⽅法之前被调⽤,不管是在初 始挂载时还是在后续组件更新时都会被调⽤。 它的调⽤时机和 componentWillMount、componentWillUpdate、componentWillReceiveProps ⼀ 样都是在 render ⽅法被调⽤之前,它可以作为 componentWillMount、componentWillUpdate 和 componentWillReceiveProps 的替代⽅案。 当然,它的作⽤不⽌如此,它可以返回⼀个对象,⽤来更新 state,就像它的名字⼀样,从 props 中获 取衍⽣的 state。如果不需要更新 state 则可以返回 null。 需要注意的是:这个⽣命周期函数是类的静态⽅法,并不是原型中的⽅法,所以在其内部使⽤ this 访 问到的不是组件实例。 此⽣命周期钩⼦不常⽤,如果可以的话,我们也尽可能不会使⽤它。 关于为什么要废弃 componentWillMount、componentWillUpdate、componentWillReceiveProps 三个⽣命周期钩⼦⽽新增 getDerivedStateFromProps ⽣命周期钩⼦,后⾯会说明原因。

shouldComponentUpdate

在组件准备更新之前调⽤,但是⾸次渲染或者使⽤ forceUpdate 函数时不会被调⽤。跟它的名字⼀ 样,它⽤来判断⼀个组件是否应该更新。 默认情况下,当组件的 props 或者 state 变化时,都会导致组件更新。它在 render ⽅法之前执⾏,如 果它的返回值为 false,则不会更新组件,也不会执⾏后⾯的 render ⽅法。 它接收两个参数,nextProps 和 nextState,即下⼀次更新的 props 和下⼀次更新的 state。我们可以 将 this.props 和 nextProps ⽐较,以及将 this.state 与 nextState ⽐较,并返回 false,让 组件跳过更新。不过注意:它并不会阻⽌⼦组件因为 state 改变⽽导致的更新。 使⽤场景: 这个⽣命周期⽅法通常⽤来做性能优化。

componentWillMount(UNSAFE)

在组件挂载⾄ DOM 之前调⽤,并且只会调⽤⼀次。它在 render ⽅法之前调⽤,因此在 componentWillMount 中调⽤ this.setState 不会触发额外的渲染。 这个⽣命周期钩⼦使⽤频率较⼩,因为我们⼀般在 constructor 中初始化 state,在 componentDidMount 中引⼊副作⽤或者订阅内容。 componentWillUpdate(UNSAFE) 在组件即将更新之前执⾏,如果 shouldComponentUpdate 函数返回 false,则不会调⽤ componentWillUpdate ⽅法。 这个⽣命周期钩⼦和 componentWillMount 类似,执⾏的时机是相同的,只不过 componentWillMount 在组件⾸次渲染时执⾏,⽽ componentWillUpdate 在组件后续更新时执⾏。 这两个⽣命周期函数都不经常使⽤。

render

render ⽅法是类组件中唯⼀必须实现的⽅法,它的返回值将作为⻚⾯渲染的视图。render 函数应该为 纯函数,也就是对于相同的 state 和 props,它总是返回相同的渲染结果。 render 函数被调⽤时,会返回以下四种类型之⼀: • React 元素:通常为 JSX 语法。例如:

、 等等。 • 数组或者 fragments:render ⽅法可以通过数组返回多个元素。 • Portals:渲染⼦节点⾄不同的⼦树中。 • 字符串或者数值:会作为⽂本节点被渲染。 • boolean 类型或者 null:什么都不渲染。 需要注意的是:如果 shouldComponentUpdate ⽣命周期钩⼦返回 false,则 render ⽅法(render 阶段后续⽣命周期钩⼦)不会执⾏。

commit 阶段

commit 阶段在⾸次渲染时会执⾏ componentDidMount,在组件更新时会执⾏ getSnapshotBeforeUpdate 和 componentDidUpdate。

componentDidMount

该⽣命周期⽅法会在组件挂载之后执⾏,也只会执⾏⼀次,也就是将组件对应的 DOM 插⼊ DOM 树中 之后调⽤。它会在浏览器更新视图之前调⽤,如果在 componentDidMount 中直接调⽤ this.setState ,它会触发额外的渲染,会再⼀次调⽤ render 函数,但是浏览器中视图的更新只 会执⾏⼀次。 使⽤场景: 依赖于 DOM 的初始化操作应该放在这⾥,此外,我们⼀般在这个⽣命周期⽅法中发送⽹络请求、添加 订阅等。

getSnapshotBeforeUpdate

此⽣命周期函数在最近⼀次渲染提交⾄ DOM 树之前执⾏,此时 DOM 树还未改变,我们可以在这⾥获 取 DOM 改变前的信息,例如:更新前 DOM 的滚动位置。 它接收两个参数,分别是:prevProps、prevState,上⼀个状态的 props 和上⼀个状态的 state。它的 返回值将会传递给 componentDidUpdate ⽣命周期钩⼦的第三个参数。 使⽤场景: 需要获取更新前 DOM 的信息时。例如:需要以特殊⽅式处理滚动位置的聊天线程等。

componentDidUpdate

在组件更新后⽴即调⽤,⾸次渲染不会调⽤该⽅法。它的执⾏时机和 componentDidMount ⼀致,只 是 componentDidMount 在⾸次渲染时调⽤,⽽ componentDidUpdate 在后续的组件更新时调⽤。 可以在这个⽣命周期中直接调⽤ this.setState ,但是必须包裹在⼀个条件语句中,否则会导致 死循环。 componentDidUpdate 接收三个参数,分别是 prevProps、prevState、snapshot,即:前⼀个状态 的 props,前⼀个状态的 state、getSnapshotBeforeUpdate 的返回值。 如果组件实现了 getSnapshotBeforeUpdate ⽣命周期函数,则 getSnapshotBeforeUpdate 的返回值 将作为 componentDidUpdate 的第三个参数。 使⽤场景: 在这个⽣命周期⽅法中,可以对 DOM 进⾏操作或者进⾏⽹络请求。

componentWillUnmount

这个⽣命周期函数会在组件卸载以及销毁之前调⽤。 使⽤场景: 通常⽤来执⾏组件的清理操作,例如:清除 timer、取消⽹络请求、清除订阅等。

-------为什么废弃三个生命周期函数

React 在 16.3 版本中:

• 将 componentWillMount、componentWillReceiveProps、componentWillUpdate 三个⽣命周 期钩⼦加上了 UNSAFE 前缀,变为 UNSAFE_componentWillMount、 UNSAFE_componentWillReceiveProps 和 UNSAFE_componentWillUpdate。 • 并引⼊了⼀个新的⽣命周期钩⼦:getDerivedStateFromProps。 并在 17.0 以及之后的版本中: • 删除了 componentWillMount、componentWillReceiveProps、componentWillUpdate 这三个 ⽣命周期钩⼦。 • 不过 UNSAFE_componentWillMount、UNSAFE_componentWillReceiveProps 和 UNSAFE_componentWillUpdate 还是可以⽤的。

为什么要废弃这三个⽣命周期钩⼦?它们有哪些问题呢?React ⼜是如何解决的呢? 我们知道 React 的更新流程分为:render 阶段和 commit 阶段。componentWillMount、 componentWillReceiveProps、componentWillUpdate 这三个⽣命周期钩⼦都是在 render 阶段执⾏ 的。

在 fiber 架构被应⽤之前,render 阶段是不能被打断的。当⻚⾯逐渐复杂之后,就有可能会阻塞⻚⾯ 的渲染,于是 React 推出了 fiber 架构。在应⽤ fiber 架构之后,低优先级任务的 render 阶段可以被⾼ 优先级任务打断。

⽽这导致的问题就是:在 render 阶段执⾏的⽣命周期函数可能被执⾏多次。像 componentWillMount、componentWillReceiveProps、componentWillUpdate 这三个⽣命周期钩 ⼦,如果我们在其中执⾏⼀些具有副作⽤的操作,例如发送⽹络请求,就有可能导致⼀个同样的⽹络 请求被执⾏多次,这显然不是我们想看到的。

⽽ React ⼜没法强迫开发者不去这样做,因为怎么样使⽤ React 是开发者的⾃由,所以 React 就新增 了⼀个静态的⽣命周期 getDerivedStateFromProps,来解决这个问题。

⽤⼀个静态函数 getDerivedStateFromProps 来取代被废弃的⼏个⽣命周期函数,这样开发者就⽆法 通过 this 获取到组件的实例,也不能发送⽹络请求以及调⽤ this.setState 。它就是强制开发者 在 render 之前只做⽆副作⽤的操作,间接强制我们⽆法进⾏这些不合理不规范的操作,从⽽避免对⽣ 命周期的滥⽤。