在场景很复杂的情况,当一个组件更新,该如何去更新其他的组件呢?这是很难搞清楚的问题,因此,我们需要每个组件自己对当前的情况作出响应,也就是说当组件开始联动的时候,我们不需要分析出该组件影响了多少组件,分别是那个组件,而是让每一个组件管理好自己就好了。为此,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的生命周期,具体如下:
componentWillMountcomponentWillReceivePropscomponentWillUpdate
它们都有一个共性:处于render阶段,它是允许暂停、终止或重新启动的。
2、处于render阶段,会导致什么问题?
在Fiber机制下,当一个任务执行一半被打断后,再次拿到主动权时,它是:重复执行一遍整个任务,也就是它被重启的方式不是接着上次执行继续。这就导致了处在render阶段的生命周期的任务有可能被重复执行
换个说法,假设一个付款请求,用户点击付款,需要支付10元,可能因为componentWill被打断重启多次,导致多次调用付款请求,最终需要支付40元