React组件生命周期

借用网上找的图
视图类View及子类的继承关系图

生命周期钩子函数

constructor

在类组件创建实例时调用,初始化的时候执行一次,可以在组件进行初始化时候控制组件。不定义 constructor,React 会自动为组件创建一个默认的 constructor。在这个阶段通常会做一下操作:

初始化 state ,获取路由中的参数,组件传值赋值给 state等 。
绑定类组件事件,绑定 this 、节流,防抖。
对类组件进行生命周期的劫持,渲染劫持等。

getDerivedStateFromProps

组件更新执行包括(props变化,setState、forceUpdate),需要注意的是 getDerivedStateFromProps 被定义为 static 方法 —— static 方法内部拿不到组件实例的 this。

render

React最重要的步骤,创建虚拟DOM,进行diff算法,更新DOM树都在此进行。此时就不能更改state了。不要在 render() 里面使用 setState,否则会触发死循环导致内存崩溃。

componentDidMount

组件挂载到 DOM 后立即被调用,与constructor 类似,但是此时可以操作dom。

shouldComponentUpdate

判断是否需要重新渲染组件,主要依赖传入的props 或 state 来决定是否更新组件

getSnapshotBeforeUpdate

getSnapshotBeforeUpdate必须与 componentDidUpdate 一起使用,因为它的返回值会作为第三个参数传递给 componentDidUpdate。

我们在大多数情况下不使用,它主要用于处理一些处理复杂的 UI 动画或需要手动保存和恢复某些 DOM 状态的情况。

componentDidUpdate

组件更新(re-render)完成之后被调用

componentWillUnmount

组件销毁阶段唯一执行的生命周期,通常用于清除定时器或者dom事件回调。

componentWillMount,componentWillReceiveProps 和 componentWillUpdate

componentWillMount

它是在 render 之前执行的,对于是否执行 render 可以通过 shouldUpdate 去制约,但是对于 componentWillMount 这个生命周期钩子,在多次触发 updateClassInstance 时,并没有条件限制,因此存在生命周期内的上下文被多次执行的风险。

componentWillReceiveProps

当新旧 props 变化时会执行 componentWillReceiveProps,但是父组件的 render 函数执行时,即便子组件 props 未改变,也会导致 componentWillReceiveProps 生命周期执行
这是因为对于新旧 props 的比较仅仅是比较内存地址是否发生变化,而父组件 render 函数执行时,子组件 props 重新创建,即便对象的属性没发生变化,但已经是另外一个对象了,从而导致 componentWillReceiveProps 执行

componentWillUpdate

推荐使用 getSnapshotBeforeUpdate 生命周期替代

执行顺序

挂载阶段

父组件:rconstructor()
父组件:static getDerivedStateFromProps()
父组件:render()
子组件:rconstructor()
子组件:static getDerivedStateFromProps()
子组件:render()
子组件:componentDidMount()
父组件:componentDidMount()

更新阶段

父组件:static getDerivedStateFromProps()
父组件:shouldComponentUpdate()
父组件:render()
父组件:getSnapshotBeforeUpdate()
父组件:componentDidUpdate()
子组件:static getDerivedStateFromProps()
子组件:shouldComponentUpdate()
子组件:render()
子组件:getSnapshotBeforeUpdate()
子组件:componentDidUpdate()

卸载阶段

子组件:componentWillUnmount()
父组件:componentWillUnmount()

函数组件中的替代方案

函数组件中不存在类组件的生命周期,但可以通过 hooks 去替代实现,首先看看相关 hooks 的特性方便理解如何替代类组件生命周期

useEffect & useLayoutEffect

useEffect 是异步执行的,不会阻塞浏览器渲染;useLayoutEffect 是同步执行的,在 DOM 更新之后,浏览器渲染绘制之前执行,如果执行的任务比较耗时则有可能阻塞浏览器渲染。

需要修改 DOM,改变页面布局就使用 useLayoutEffect,否则都使用 useEffect。

componentDidMount => useEffect

利用 useEffect 第二个参数 dep 数组为空的特性,当 dep 为空数组时,useEffect 的回调只会执行一次。

componentWillUnmount => useEffect

利用 useEffect 回调中返回的函数会在组件销毁前执行的特性模拟 componentWillUnmount。同样,要让 dep 数组为空数组才能让其只执行一次。

componentWillReceiveProps => useEffect

严格来说,说 useEffect 可以替代 componentWillReceiveProps 是比较牵强的,理由如下:

useEffect 发生在 commit 阶段,而 componentWillReceiveProps 则发生在 render 阶段
useEffect 会在初始化时执行一次,而 componentWillReceiveProps 并不会

但是由于 useEffect 的 dep 数组可以起到一个监听的作用,我们可以利用它来监听 props 或 props 中具体某个属性的变化,因而从功能作用上来看确实是可以替代 componentWillReceiveProps

componentDidUpdate => useEffect

与替代 componentWillReceiveProps 类似,只能说 useEffect 在功能作用上是可以替代 componentDidUpdate,但是在底层原理上不算是严格替代 componentDidUpdate。

另外 componentDidUpdate 是同步执行的,而 useEffect 是异步执行的,但它们的执行时机都是在 commit 阶段,所以只要不传递 useEffect 的第二个参数即可在每次函数组件执行时执行 useEffect 里的回调

[越努力,越幸运!]