在场景很复杂的情况,当一个组件更新,该如何去更新其他的组件呢?这是很难搞清楚的问题,因此,我们需要每个组件自己对当前的情况作出响应,也就是说当组件开始联动的时候,我们不需要分析出该组件影响了多少组件,分别是那个组件,而是让每一个组件管理好自己就好了。为此,React
为每个组件都提供了生命周期,让每个组件都管好自己
当一个组件被其他组件影响的时候,组件大致会分为三个阶段:
- 组件挂载
- 组件更新
- 组件卸载
每个阶段会做些什么,适合做什么?React 16
又为何要改造生命周期?下面我们从React 15
的生命周期启程,进行探索
一、React 15
生命周期
1、挂载阶段(Mounting)
挂载阶段在组件的一生中仅执行一次,在这个过程中,组件被初始化,接着被渲染到真实的DOM
中,完成“首次渲染”。也就是页面刚打开时组件完成初始化渲染的过程。它会按顺序执行下面的过程:
constructor
如果不初始化 state
或不进行方法绑定,则不需要为 React
组件实现构造函数
componentWillMount
注意:在这里进行初始化操作有风险
本想让请求返回的“早一些”,避免首次渲染白屏,便在这里发起异步请求。可异步再怎么快也快不了同步的生命周期。componentWillMount
结束后render
被迅速触发,因此首次渲染依然会在数据返回之前执行,导致了在服务端渲染的场景下,出现冗余请求的问题。
同时,在这里滥用setState
可能会导致重复渲染进入死循环...邪魅一笑!
render
把需要渲染的内容返回,且必须返回单个元素,不会操作真实的DOM
。真实DOM
的渲染工作,由ReactDOM.render
承接
componentDidMount
此时真实的DOM
已经挂载在页面了,可在此执行真实的DOM
的相关操作,此外,异步请求、数据初始化等操作也可以在此进行
2、更新阶段(Updating)
更新阶段主要由父组件更新 或 组件自身调用setState
而触发更新
- 由父组件触发:
componentWillReceiveProps
React官方对componentWillReceiveProps
的描述:“如果父组件导致组件重新渲染,即使props没有更改,也会调用此方法。如果只想处理更改,请确保进行当前值与变更值的比较”
即:它是由父组件的更新触发,而不是由props的变化触发
在上面的demo中,我们在父组件添加一个仅仅用于父组件的ownText
,并修改它,验证componentWillReceiveProps
是否被触发了:
- 由组件自身触发:
shouleComponentUpdate
render
方法由于伴随着对虚拟的构建和比对,其过程相对耗时,为了避免不必要的操作带来的性能开销,React
给开发者提供了一个控制的入口:shouleComponentUpdate
,组件会根据它的返回值来决定是否执行该方法之后的生命周期,进而决定是否对组件进行重渲染,其默认值为true,即无条件重渲染
componentWillUpdate
允许你做一些不涉及真实DOM
的准备工作
componentDidUpdate
组件在更新完毕后被触发,可处理DOM
操作,此外常将它的执行作为子组件更新完毕的标志通知到父组件
3、卸载阶段(UnMounting)
当 组件在父组件中被移除 或 组件设置了key
属性,父组件在render
的过程中,发现key
值和上一次不一致 则需要将其移除,这就涉及到卸载阶段,其只涉及一个生命周期:componentWillUnmount
点击react15生命周期Demo可以看到上面我们结合的例子。下面,我们进一步探究一下React 16
生命周期并找出其差异及背后的野心
二、React 16
生命周期
相对于React15
,React16
同样将生命周期划分为“挂载”、“更新”和“卸载”三阶段,其中“卸载”阶段跟React15
是无异的,而“挂载”和“更新”阶段进行了“废旧立新”的变化。
1、挂载阶段(Mounting)
与React15
比对,区别在于:废 componentWillMount
立 getDerivedStateFromProps
。
函数名直译即是:从props
里派生state
。可见:React16
在强制推行“只用getDerivedStateFromProps
来完成props
到state
的映射”,确保函数行为更加可控可预测,从根源避免生命周期滥用。使用它,需把握四点:
-
它是一个静态方法,不依赖组件实例而存在,因此在方法内部是访问不到this的
-
其接收的两个参数:
props
和state
,分别代表当前组件接收到来自父组件的props
和当前组件自身的state
-
必须返回一个对象格式的返回值,因为
React
需要用这个返回值更新(派生)组件的state
。因此当你不存在“使用props
派生state
”的需求时,最好直接省略该生命周期,否则记得返回一个null
-
其对
state
的更新并非“覆盖”式的更新,而是针对某个属性的定向更新,即更新后,原有属性与新属性是共存的
派生state
这个诉求不仅在props
初始化时存在,在更新时也是存在的,它的设计初衷并不是替代componentWillMount
,而是试图对更新阶段的componentWillReceiveProps
做“合理的减法”,实现基于props
派生state
。
React
官方给出的描述:“与componentDidUpdate
一起,这个新的生命周期涵盖过时componentWillReceiveProps
的所有用例”
2、更新阶段(Updating)
与React15
比对,区别在于:废 componentWillReceiveProps
、componentWillUpdate
立 getDerivedStateFromProps
、getSnapshotBeforeUpdate
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元