React进阶笔记【2_生命周期】

2022/4/5 ReactJS

# 组件

  • 封闭

    在组件自身的渲染工作流中,每个组件都只处理它内部的渲染逻辑。

  • 开放

    React 允许开发者基于 “单向数据流” 的原则完成组件之间的通信,而组件之间的通信又将改变通信双方/某一方内部的数据,进而对渲染结果构成影响。

# React 15 生命周期

在组件更新中,如果父组件导致组件重新渲染,即使 props 没有更改,也会调用此方法(componentReceiveProps)。如果只想处理更改,请确保进行当前值与变更值的比较。

​ —— React官方

  • componentReceiveProps 不是由 props 的变化触发的,而是由父组件的更新触发的。

  • React 组件会根据 shouldComponentUpdate 的返回值来决定是否执行该方法之后的生命周期,进而决定是否对组件进行 re-render(重渲染)。shouldComponentUpdate 默认为 true,表示无条件 re-render。

  • componentWillUnmount 有两种触发方式:

    • 组件在父组件中被移除了
    • 组件中设置了 key 属性,父组件在 render 过程中,发现 key 不一致

# React 16 生命周期

getDerivedStateFromProps 设计的初衷不是为了替换 componentWillMount,而是为了替换 componentWillReceiveProps,所以 getDerivedStateFromProps 有且仅有一个用途:使用 props 来派生/更新 state。 并且在挂载和更新时都用到了它。

# getDerivedStateFromProps

  1. 它是一个静态方法,静态方法不依赖组件实例而存在,所以在该方法内部是访问不到 this 的。
  2. 该方法接收两个参数:props 和 state。props 表示当前组件接收到的父组件 props,state表示当前组件自身的 state。
  3. 它需要一个对象形式的返回值,如果没有会被警告。React 需要该返回值来更新组件的 state,所以如果没有的话,至少需要返回一个 null

在 React 16.4+,任何原因触发的组件更新,都会触发 getDerivedStateFromProps,而在 React 16.3,只有父组件更新才会触发 getDerivedStateFromProps

# 为什么要用 getDerivedStateFromProps 来代替 componentWillReceiveProps ?

原则上来说,原来的 componentWillReceiveRrops 可以做很多事,而 getDerivedStateFromProps 能做且只能做一件事,就是基于 props 派生 state,这一点在它的定义上也可以看出来(使用了 static):

class Name extends React.Component {
  static getDerivedStateFromProps(props, state){
  }
}
1
2
3
4

static 限制了 getDerivedStateFromProps 里拿不到 this,也就意味着无法在该方法里做类似 setState 等可能会产生副作用的操作。

所以,React 16 在强制推行:“只用 getDerivedStateFromProps 来完成 props 到 state 的映射”。 在开发方式上避免了开发者对生命周期的滥用,同时也在为 Fiber 架构铺路。

# getSnapshotBeforeUpdate

getSnapshotBeforeUpdategetDerivedStateFromProps 有点类似,它的返回值会作为第三个参数,给到 componentDidUpdate,它的执行时机是在 render 方法后,真实 DOM 更新之前。同时获取到更新前的真实 DOM 和更新前后的 state 和 props 信息。

class Name extends React.Component {
  // 组件更新时调用
  getSnapshotBeforeUpdate(prevProps, prevState){
    console.log('aaaa')
    return 'hhh'
  }
  // 组件更新后调用
  componentDidUpdate(prevProps, prevState, valueFromSnapshot){
    console.log('bbb')
    console.log('从 getSnapshotBeforeUpdate 传过来的值是', valueFromSnapshot)
  }
}

-->
// aaa
// bbb
// 从 getSnapshotBeforeUpdate 传过来的值是 hhh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

砍掉 componnetWillUpdate 是为了给 Fiber 铺路。

# Fiber 简介

Fiber 是 React 16 对 React 核心算法的一次重写,Fiber 会使原本同步的渲染过程变成异步执行。

Fiber 架构的重要特征就是可以被打断的异步渲染模式,根据 “能否被打断” 这一标准,React 16 的生命周期被划分为了 render 和 commit 两个阶段(commit 又分为 pre-commit 和 commit)。

  1. render 阶段

    纯净且不包含副作用,可能会被 React 暂停、终止或重新启动。

  2. pre-commit 阶段

    可以读取 DOM。

  3. commit 阶段

    可以使用 DOM,运行副作用,安排更新。

当一个任务执行到一半被打断后,下一次渲染线程抢回主动权时,该任务被重启的形式是重复执行一遍整个任务,而不是从打断的那行代码开始执行。这就会导致 render 阶段的生命周期都是有可能被重复执行的。

该阶段对于用户是不可见的,所以打断再启动,对于用户也是无感知的。

再看废弃的 componentWillMountcomponentWillUpdatecomponentWillReceiveProps,废弃的原因是它们都处于 render 阶段,都可能被重复执行,而且这些 API 都有着被滥用的风险。

componentWill 开头的生命周期里,可能会喜欢做:setState、fetch发起请求、操作真实DOM等,这些操作的问题包括但不限于以下三点:

  1. 这些完全可以转移到其他生命周期(尤其是 componentDidxxx)里去做。异步请求再快也快不过同步的生命周期,componentWillxxx 执行的时候,render 会迅速触发,所以首次渲染依然会在数据返回之前执行(也就是白屏问题)。
  2. 在 Fiber 的异步渲染机制下,可能会导致非常严重的 Bug。由于 render 阶段的生命周期都可以重复执行,在 componentWillxxx 被 打断/重启 多次后,就会触发多个请求。
  3. 即使没有开启异步,React 15 下也会有很多人把自己 “玩死”。在 componentWillReceivePropscomponentWillUpdate 里滥用 setState, 会导致重复渲染死循环。 React 16 改造生命周期的主要动机是为了配合 Fiber 架构带来的异步渲染机制。