为什么React16要更改组件的生命周期?
我将抱着帮你做到“知其所以然”的目的,以 React 的基本原理为引子「我将抱着帮你做到“知其所以然”的目的,以 React 的基本原理为引子,对 React 15、React 16 两个版本的生命周期进行探讨、比对和总结,通过搞清楚一个又一个的“Why”,来帮你建立系统而完善的生命周期知识体系。」
生命周期背后的设计思想:把握React中的“大方向”
在介绍具体的生命周期之前,我想先带着你初步了解 React 框架中的一些关键的设计思想,以便为你后续的学习提供不可或缺的“加速度”。
如果你经常翻阅 React 官网的文章,你会发现**“组件”和“虚拟DOM”** 这两个词的出镜率是非常高,它们是 React 基本原理中极为关键的两个概念,也是我们这次深入学习生命周期的切入点。
虚拟DOM: 核心算法的基石
在jsx部分,我们已经知道了虚拟DOM的基本形态(不太了解的可以先翻阅前一篇文章)。我们需要简单了解下虚拟 DOM 在整个 React 工作流中的作用。
组件在初始化时,会调用生命周期中的 render 方法,「生成虚拟DOM」 ,然后再通过 「ReactDOM.render」 方法,实现虚拟DOM到真实DOM的转换
当组件更新时,会再次通过 render 方法生成 「新的虚拟DOM」,然后借助 「diff」(这是一个非常关键的算法,后面我会专门讲解)「定位出两次虚拟DOM的差异」,从而针对发生变化的真实DOM做定向更新
以上就是React 框架算法的大致流程,对于这套关键的工作流来说,「“虚拟DOM”」 是所有操作的大前提,是核心算法的基石。
组件化:工程化思想在框架中的落地
组件化是一种优秀的软件设计思想,也是 React 团队在研发效能方面所做的一个重要的努力。
在一个 React 项目中,几乎所有的可见/不可见的内容都可以被抽离为各种各样的组件,每个组件既是“封闭”的,也是“开放”的。
所谓“封闭”,主要是针对“渲染工作流”(指从组件数据改变到组件实际更新发生的过程)来说的。在组件自身的渲染工作流中,每个组件都只处理它内部的渲染逻辑。在没有数据流交互的情况下,组件与组件之间可以做到“各自为政”。
而所谓“开放”,则是针对组件间通信来说的。React 允许开发者基于“单向数据流”的原则完成组件间的通信。而组件之间的通信又将改变通信双方/某一方内部的数据,进而对渲染结果构成影响。所以说在数据这个“红娘”的牵线搭桥之下,组件之间又是彼此开放的,是可以相互影响的。
这一“开放”与“封闭”兼具的特性,使得 React 组件既专注又灵活,具备高度的可重用性和可维护性。
生命周期方法的本质:组件的“灵魂”与“躯干”
- 「render方法为React组件的灵魂」
- 「其他生命周期方法为躯干」
注意:这里提到的render 方法,和之前说的 「ReactDOM.render」 不是一个东西,它指的是 React 组件内部的这个生命周期方法:
class LifeCycle extends React.Component {
render() {
console.log("render方法执行");
return (
<div className="container">
this is contentdiv>
}
}
前面咱们介绍了虚拟 DOM、组件化,倘若把这两块知识整合一下,你就会发现这两个概念似乎都在围着 render 这个生命周期打转:虚拟 DOM 自然不必多说,它的生成都要仰仗 render;而组件化概念中所提及的“渲染工作流”,这里指的是从组件数据改变到组件实际更新发生的过程,这个过程的实现同样离不开 render。
由此看来,render 方法在整个组件生命周期中确实举足轻重,它担得起“灵魂”这个有分量的比喻。那么如果将 render 方法比作组件的“灵魂”,render 之外的生命周期方法就完全可以理解为是组件的“躯干”。
“躯干”未必总是会做具体的事情(比如说我们可以选择性地省略对 render 之外的任何生命周期方法内容的编写),而“灵魂”却总是充实的(render 函数却坚决不能省略);倘若“躯干”做了点什么,往往都会直接或间接地影响到“灵魂”(因为即便是 render 之外的生命周期逻辑,也大部分是在为 render 层面的效果服务);“躯干”和“灵魂”一起,共同构成了 React 组件完整而不可分割的“生命时间轴”。
拆解React生命周期:从React15说起
Mounting 阶段:组件的初始化渲染(挂载)
挂载过程在组件的一生中仅会发生一次,在这个过程中,组件被初始化,然后会被渲染到真实 DOM 里,完成所谓的“首次渲染”。
在挂载阶段,一个 React 组件会按照顺序经历如下图所示的生命周期:
首先我们来看 constructor 方法,该方法仅仅在挂载的时候被调用一次,我们可以在该方法中对 this.state 进行初始化:
constructor(props) {
console.log("进入constructor");
super(props);
// state 可以在 constructor 里初始化
this.state = { text: "子组件的文本" };
}
componentWillMount、componentDidMount 方法同样只会在挂载阶段被调用一次。其中 componentWillMount 会在执行 render 方法前被触发,一些同学习惯在这个方法里做一些初始化的操作,但这些操作往往会伴随一些风险或者说不必要性(这一点大家先建立认知)。
接下来 render 方法被触发。注意 render 在执行过程中并不会去操作真实 DOM(也就是说不会渲染),它的职能是「把需要渲染的内容返回出来」。真实 DOM 的渲染工作,在挂载阶段是由 ReactDOM.render 来承接的。
componentDidMount 方法在渲染结束后被触发,此时因为真实 DOM 已经挂载到了页面上,我们可以在这个生命周期里执行真实 DOM 相关的操作。此外,类似于异步请求、数据初始化这样的操作也大可以放在这个生命周期来做(侧面印证了 componentWillMount 真的很鸡肋)。
这一整个流程对应的其实就是我们 Demo 页面刚刚打开时,组件完成初始化渲染的过程。下图是 Demo 中的 LifeCycle 组件在挂载过程中控制台的输出,你可以用它来验证挂载过程中生命周期顺序的正确性:
Updating 阶段:组件的更新
组件的更新分为两种:一种是由父组件更新触发的更新;另一种是组件自身调用自己的 setState 触发的更新。这两种更新对应的生命周期流程如下图所示: