React 生命周期版本进化史

298 阅读8分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情

前言

脑袋和嘴巴都经常瓢,干脆再整理一下 ····<*)) >>=<

一、不同版本生命周期

16.3 版本之前

image.png

16.3 版本之后

image.png

React 16.3 版本:

  • 为不安全的生命周期引入别名,UNSAFE_componentWillMountUNSAFE_componentWillReceivePropsUNSAFE_componentWillUpdate。(旧的生命周期名称和新的别名都可以在此版本中使用。)
  • 引入两个新的生命周期,静态的 getDerivedStateFromProps 和 getSnapshotBeforeUpdate

v16.3.0

  • Add a new getDerivedStateFromProps() lifecycle and UNSAFE_ aliases for the legacy lifecycles. (@bvaughn in #12028)
  • Add a new getSnapshotBeforeUpdate() lifecycle. (@bvaughn in #12404)

React 16.3 之后的版本:

  • 为 componentWillMount、componentWillReceiveProps 和 componentWillUpdate 启用废弃告警。(旧的生命周期名称和新的别名都将在这个版本中工作,但是旧的名称在开发模式下会产生一个警告。)

React 16.9 版本:

  • 删除 componentWillMount、componentWillReceiveProps 和 componentWillUpdate。(在此版本之后,只有新的 “UNSAFE_” 生命周期名称可以使用。之后版本“UNSAFE_” 开发模式下会产生一个警告。)

16.9.0 (August 8, 2019)

  • Deprecate old names for the UNSAFE_* lifecycle methods.

注意

  • UNSAVE_前缀的三个函数,目的是为了向下兼容,但是对于开发者而言应该尽量避免使用它们,而是使用新增的生命周期函数替代它们。

二、三个阶段

  1. 挂载阶段:组件即将开始初始化并挂载到 DOM 的阶段。
  2. 更新阶段:一旦组件被添加到 DOM,它只有在 prop 或 state 状态发生变化时才可能更新和重新渲染。这些只发生在这个阶段。
  3. 卸载阶段:组件被销毁并从 DOM 中删除。
16.3 生命周期特点
挂载阶段
constructor组件完成 React 数据的初始化。
static getDerivedStateFromProps组件初始渲染,调用 render () 之前调用。
render组件第一次渲染dom
componentDidMount组件第一次渲染完成,此时dom节点已经生成。
更新阶段
static getDerivedStateFromProps组件props更新后触发。
shouldComponentUpdate当 props 或 state 发生变化时触发。
render重新渲染。
getSnapshotBeforeUpdate在render() 之后,componentDidUpdate之前调用。
componentDidUpdate在更新后会被立即调用。
卸载阶段
componentWillUnmount组件卸载和销毁。

三、16.3 之后钩子函数

1、挂载阶段

1.1 constructor

代表过程:构造函数,最先被执行;完成 React 数据的初始化

语法

  • 接收两个参数:propscontext

使用场景:一般用来操作 props 和初始化 state,或者给自定义方法绑定this。

import React from "react";
import moment from "moment";

export default class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      currentYear: moment().year(), 
      contries:['beijing','shanghai']
    };
    this.getValue = this.getValue.bind(this);
  }

  getValue(contryId) {
    return this.state.contries[contryId];
  }
}

注意

  1. 无论有没有 constructor, render 中都可以使用 this.props, 默认自带。
  2. 如果组件没有声明 constructor, react 会默认添加一个空的 constructor。
  3. 如果在 constructor 中要使用 this.props, 就必须给 super 加参数: super(props)。
  4. super 应该在其他语句之前调用。
  5. 写了 constructor 必须有 super,否则 class 组件会报错
// 报错:
ReferenceError: Must call super constructor in derived class before 
  accessing 'this' or returning from derived constructor
  
constructor() {
  super();
}
或者
constructor(props) {
  super(props);
}
  1. 在 constructor() 函数中不要调用 setState() 方法,请直接在构造函数中为 this.state 赋值初始化 state。如需在其他方法中赋值,你应使用 this.setState() 替代。

1.2 static getDerivedStateFromProps

代表过程:会再调用 render () 之前调用,并且在初始挂载后续更新时都会被调用。即每次渲染之前都会触发,后续 props 更新也会触发。

语法:接收更新后的 props更新前的 state 参数,返回一个对象来更新 state,如果返回 null 则不更新任何内容。

static getDerivedStateFromProps(nextProps, prevState)
nextProps:更新后props
prevState:更新前state

使用场景:如果 props 传入的内容不需要影响到你的 state,那么就需要返回一个 null,这个返回值是必须的,所以尽量将其写到函数的末尾。

static getDerivedStateFromProps(nextProps, prevState) {
  const { type } = nextProps;
  // 当传入的type发生变化的时候,更新state
  if (type !== prevState.type) {
    return { type };
  }
  // 否则,对于state不进行任何操作
  return null;
}

注意

  1. static 不允许用 this,也就是这个函数不能通过 this 访问到 class 的属性,而是应该通过参数提供的 nextProps 以及 prevState 来进行判断,根据新传入的 props 来映射到 state。
  2. 由于16.4的修改,这个函数会在每次re-rendering之前被调用。即使你的 props 没有任何变化,而是父 state 发生了变化,导致子组件发生了 re-render,这个生命周期函数依然会被调用

1.3 render()

代表过程初次渲染Dom。

注意

  1. render函数是纯函数,只返回需要渲染的东西,不应该包含其它的业务逻辑。
  2. 返回的类型:
  • 原生的 DOM
  • React元素:JSX 或者 自定义React组件。
  • 数组或者 Fragment
  • Portals
  • 字符串或数值类型:在DOM中被渲染为文本节点。
  • Boolean和null:什么都不渲染。

1.4 componentDidMount

代表过程:会在组件挂载后(插入 DOM 树中)立即调用,即组件第一次渲染完成后会调用,此时dom节点已经生成。

componentDidMount?(): void;

使用场景:组件装载之后调用,此时我们可以获取到DOM节点并操作,比如对 canvas,svg 的操作、服务器请求、监听、订阅都可以写在这个里面,但是记得在 componentWillUnmount 中取消监听、订阅。

注意

  1. 可以在这里调用 ajax 请求,返回数据 setState 后组件会重新渲染。但此渲染会发生在浏览器更新屏幕之前。如此保证了即使在 render() 两次调用的情况下,用户也不会看到中间状态。

2、更新阶段

触发时机:当组件的 propsstate 发生变化时会触发更新。

2.1 shouldComponentUpdate

代表过程:当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染render执行之前被调用。

语法:返回一个布尔值,true表示会触发重新渲染,false表示不会触发重新渲染,默认返回true,我们通常利用此生命周期来优化 React 程序性能。

shouldComponentUpdate?(nextProps: Readonly<P>, nextState: Readonly<S>, nextContext: any): boolean;
nextProps: 更新后props
nextState: 更新后state

注意

  1. 唯一用于控制组件重新渲染的生命周期,由于在react中,setState以后,state发生变化,组件会进入重新渲染的流程,在这里return false可以阻止组件的更新。
  2. 因为 react 父组件的重新渲染会导致其所有子组件的重新渲染,这个时候其实我们是不需要所有子组件都跟着重新渲染的,因此需要在子组件的该生命周期中做判断。

2.2 render()

代表过程:重新渲染,render函数会插入jsx生成的dom结构,react会生成一份虚拟dom树,在每一次组件更新时,在此react会通过其diff算法比较更新前后的新旧DOM树,比较以后,找到最小的有差异的DOM节点,并重新渲染。

2.3 getSnapshotBeforeUpdate

代表过程:在render() 之后,componentDidUpdate之前调用,react只会在第一次初始化成功会进入componentDidmount,组件更新完毕后,之后每次重新渲染后都会进入getSnapshotBeforeUpdate。

语法:这个函数有一个返回值,会作为第三个参数传给componentDidUpdate。

getSnapshotBeforeUpdate?(prevProps: Readonly<P>, prevState: Readonly<S>): SS | null;
prevProps: 更新前props
prevState: 更新前state

2.4 componentDidUpdate

代表过程:在更新后会被立即调用,首次渲染不会执行此方法。

componentDidUpdate?(prevProps: Readonly<P>, prevState: Readonly<S>, snapshot?: SS): void;

注意

  1. 如果 shouldComponentUpdate()返回值为 false,则不会调用 componentDidUpdate()。
  2. 在 componentDidUpdate 可以使用 this.setSate(),但必须包裹在一个条件语句中,否则会导致死循环。

3、卸载阶段

3.1 componentWillUnmount

代表过程:会在组件卸载及销毁之前直接调用。

componentWillUnmount?(): void;

使用场景

  1. 清除组件中所有的timer:setTimeout, setInterval。
  2. 取消网络请求
  3. 移除组件中的所有监听 removeEventListener。
  4. 清除在组件中创建的订阅
componentDidMount() {
    // 一般在componentDidMount创建监听和订阅
    window.addEventListener('scroll', this.xxx);
}

componentWillUnmount() {
    window.removeEventListener('scroll', this.xxx);
}

注意

  1. componentWillUnmount() 中不应调用 setState() ,因为该组件将永远不会重新渲染。组件实例卸载后,将永远不会再挂载它。

四、16.3 之前钩子函数

1、挂载阶段

一样的就不复述了。

1.1 componentWillMount(过时)

代表过程:在 constructor() 初始化数据后,render() 之前调用。因此在此方法中同步调用 setState() 不会触发额外渲染,但我们建议使用 constructor() 来初始化 state。

UNSAFE_componentWillMount(): void

注意

  1. react16.3 后(包括)使用 UNSAFE_componentWillMount。
  2. 此方法是服务端渲染唯一会调用的生命周期函数。

2、更新阶段

2.1 componentWillReceiveProps(过时)

代表过程:会在已挂载的组件接收新的 props 之前被调用。

UNSAFE_componentWillReceiveProps(nextProps, nextContext): void
nextProps: 更新后props

注意

  1. 16.3 后(包括)使用 UNSAFE_componentWillReceiveProps。
  2. 如果父组件导致组件重新渲染,即使 props 没有更改,也会调用此方法。
  3. React 不会针对初始 props 调用 UNSAFE_componentWillReceiveProps()。组件只会在组件的 props 更新时调用此方法。
  4. 调用 this.setState() 通常不会触发 UNSAFE_componentWillReceiveProps()

2.3 componentWillUpdate(过时)

代表过程:在render之前,shouldComponentUpdate之后,props 和 state 修改都会触发。

UNSAFE_componentWillUpdate?(nextProps: Readonly<P>, nextState: Readonly<S>, nextContext: any): void;
nextProps: 更新后props
nextState: 更新后state

注意

  1. 16.3 后(包括)使用 UNSAFE_componentWillUpdate。
  2. 如果 shouldComponentUpdate() 返回 false,则不会调用 UNSAFE_componentWillUpdate()。
  3. 禁止在这里使用 this.setState(),不应该执行任何操作触发对 react组件的更新。

五、父子组件生命周期

加载顺序:

挂载时:子组件的挂载钩子先被触发。

卸载时:子组件的卸载钩子后被触发。

1、父子组件初始化

Parent: constructor

Parent: getDerivedStateFromProps

Parent: render

children: constructor

children: getDerivedStateFromProps

children: render

children: componentDidMount

Parent: componentDidMount

2、父子组件卸载

Parent: componentWillUnmount

children: componentWillUnmount