在场景很复杂的情况,当一个组件更新,该如何去更新其他的组件呢?这是很难搞清楚的问题,因此,我们需要每个组件自己对当前的情况作出响应,也就是说当组件开始联动的时候,我们不需要分析出该组件影响了多少组件,分别是那个组件,而是让每一个组件管理好自己就好了。为此,React为每个组件都提供了生命周期,让每个组件都管好自己

当一个组件被其他组件影响的时候,组件大致会分为三个阶段:

  • 组件挂载
  • 组件更新
  • 组件卸载

每个阶段会做些什么,适合做什么?React 16又为何要改造生命周期?下面我们从React 15的生命周期启程,进行探索

一、React 15生命周期

3ccb9b5fbc27f560undefined

1、挂载阶段(Mounting)

挂载阶段在组件的一生中仅执行一次,在这个过程中,组件被初始化,接着被渲染到真实的DOM中,完成“首次渲染”。也就是页面刚打开时组件完成初始化渲染的过程。它会按顺序执行下面的过程:

a5b622eb4dba6535undefined

  • constructor

如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数

  • componentWillMount

注意:在这里进行初始化操作有风险

本想让请求返回的“早一些”,避免首次渲染白屏,便在这里发起异步请求。可异步再怎么快也快不了同步的生命周期。componentWillMount结束后render被迅速触发,因此首次渲染依然会在数据返回之前执行,导致了在服务端渲染的场景下,出现冗余请求的问题。

同时,在这里滥用setState可能会导致重复渲染进入死循环...邪魅一笑!

  • render

把需要渲染的内容返回,且必须返回单个元素,不会操作真实的DOM。真实DOM的渲染工作,由ReactDOM.render承接

  • componentDidMount

此时真实的DOM已经挂载在页面了,可在此执行真实的DOM的相关操作,此外,异步请求、数据初始化等操作也可以在此进行

2、更新阶段(Updating)

更新阶段主要由父组件更新 或 组件自身调用setState而触发更新

21e3c7f960484f06undefined

  • 由父组件触发:componentWillReceiveProps

React官方对componentWillReceiveProps的描述:“如果父组件导致组件重新渲染,即使props没有更改,也会调用此方法。如果只想处理更改,请确保进行当前值与变更值的比较”

即:它是由父组件的更新触发,而不是由props的变化触发

在上面的demo中,我们在父组件添加一个仅仅用于父组件的ownText,并修改它,验证componentWillReceiveProps是否被触发了:

eaba8f312a198276undefined

  • 由组件自身触发:shouleComponentUpdate

render方法由于伴随着对虚拟的构建和比对,其过程相对耗时,为了避免不必要的操作带来的性能开销,React给开发者提供了一个控制的入口:shouleComponentUpdate,组件会根据它的返回值来决定是否执行该方法之后的生命周期,进而决定是否对组件进行重渲染,其默认值为true,即无条件重渲染

  • componentWillUpdate

允许你做一些不涉及真实DOM的准备工作

  • componentDidUpdate

组件在更新完毕后被触发,可处理DOM操作,此外常将它的执行作为子组件更新完毕的标志通知到父组件

3、卸载阶段(UnMounting)

当 组件在父组件中被移除 或 组件设置了key属性,父组件在render的过程中,发现key值和上一次不一致 则需要将其移除,这就涉及到卸载阶段,其只涉及一个生命周期:componentWillUnmount

a9d695686faaf76fundefined

点击react15生命周期Demo可以看到上面我们结合的例子。下面,我们进一步探究一下React 16生命周期并找出其差异及背后的野心

二、React 16生命周期

89ecf928ac294729undefined

相对于React15React16同样将生命周期划分为“挂载”、“更新”和“卸载”三阶段,其中“卸载”阶段跟React15是无异的,而“挂载”和“更新”阶段进行了“废旧立新”的变化。

1、挂载阶段(Mounting)

9f22e5724916e188undefined 与React15比对,区别在于:废 componentWillMount 立 getDerivedStateFromProps

函数名直译即是:从props里派生state。可见:React16在强制推行“只用getDerivedStateFromProps来完成propsstate的映射”,确保函数行为更加可控可预测,从根源避免生命周期滥用。使用它,需把握四点:

  • 它是一个静态方法,不依赖组件实例而存在,因此在方法内部是访问不到this

  • 其接收的两个参数:propsstate,分别代表当前组件接收到来自父组件的props和当前组件自身的state

  • 必须返回一个对象格式的返回值,因为React需要用这个返回值更新(派生)组件的state。因此当你不存在“使用props派生state”的需求时,最好直接省略该生命周期,否则记得返回一个null

  • 其对state的更新并非“覆盖”式的更新,而是针对某个属性的定向更新,即更新后,原有属性与新属性是共存的

54547707801496bbundefined

派生state这个诉求不仅在props初始化时存在,在更新时也是存在的,它的设计初衷并不是替代componentWillMount,而是试图对更新阶段的componentWillReceiveProps做“合理的减法”,实现基于props派生state

React官方给出的描述:“与componentDidUpdate一起,这个新的生命周期涵盖过时componentWillReceiveProps的所有用例”

2、更新阶段(Updating)

d4e4d4434a4474adundefined

React15比对,区别在于:废 componentWillReceivePropscomponentWillUpdate 立 getDerivedStateFromPropsgetSnapshotBeforeUpdate

getSnapshotBeforeUpdate 可同时获取到更新前的真实DOM和更新前后的state&props信息, 同 getDerivedStateFromProps 一样强调返回值,区别在于它返回的值会作为第三个参数给到componentDidUpdate。其设计初衷则是为了“与componentDidUpdate一起,涵盖过时的componentWillUpdate的所有用例”

React官方给出的描述:“与componentDidUpdate一起,涵盖过时componentWillUpdate的所有用例”

点击react16生命周期Demo可以看到上面我们结合的例子。下面,我们进一步探究一下藏在React 16生命周期改造的why

三、为何要改造生命周期?

Fiber架构的重要特征:可以被打断的异步渲染模式。React16根据“能否被打断”这一标准,将其划分为render 和 commit 两阶段。render阶段在执行过程中允许被打断,而commit阶段则总是同步执行

  • render阶段:纯净且没有副作用,它对用户来说是“零感知”的。因此允许React在这个阶段进行暂停、终止或重新启动

  • pre-commit阶段:可以读取DOM

  • commit阶段:可以使用DOM,运行副作用,安排更新,涉及真实DOM的渲染,它是在用户眼底皮下的,任何一点动静,都是“有感知”的,因此这个过程必须用同步渲染求稳

1、React 16为什么要“废弃立新”?

从上面我们知道,React 16主要废弃了componentWill的生命周期,具体如下:

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

它们都有一个共性:处于render阶段,它是允许暂停、终止或重新启动的。

2、处于render阶段,会导致什么问题?

Fiber机制下,当一个任务执行一半被打断后,再次拿到主动权时,它是:重复执行一遍整个任务,也就是它被重启的方式不是接着上次执行继续。这就导致了处在render阶段的生命周期的任务有可能被重复执行

换个说法,假设一个付款请求,用户点击付款,需要支付10元,可能因为componentWill被打断重启多次,导致多次调用付款请求,最终需要支付40元