import React from "react";
import ReactDOM from "react-dom";
// 定义子组件
class LifeCycle extends React.Component {
constructor(props) {
console.log("进入constructor");
super(props);
// state 可以在 constructor 里初始化
this.state = { text: "子组件的文本" };
}
// 初始化/更新时调用
static getDerivedStateFromProps(props, state) {
console.log("getDerivedStateFromProps方法执行");
return {
fatherText: props.text
}
}
// 初始化渲染时调用
componentDidMount() {
console.log("componentDidMount方法执行");
}
// 组件更新时调用
shouldComponentUpdate(prevProps, nextState) {
console.log("shouldComponentUpdate方法执行");
return true;
}
// 组件更新时调用
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log("getSnapshotBeforeUpdate方法执行");
return "haha";
}
// 组件更新后调用
componentDidUpdate(preProps, preState, valueFromSnapshot) {
console.log("componentDidUpdate方法执行");
console.log("从 getSnapshotBeforeUpdate 获取到的值是", valueFromSnapshot);
}
// 组件卸载时调用
componentWillUnmount() {
console.log("子组件的componentWillUnmount方法执行");
}
// 点击按钮,修改子组件文本内容的方法
changeText = () => {
this.setState({
text: "修改后的子组件文本"
});
};
render() {
console.log("render方法执行");
return (
<div className="container">
<button onClick={this.changeText} className="changeText">
修改子组件文本内容
</button>
<p className="textContent">{this.state.text}</p>
<p className="fatherContent">{this.props.text}</p>
</div>
);
}
}
// 定义 LifeCycle 组件的父组件
class LifeCycleContainer extends React.Component {
// state 也可以像这样用属性声明的形式初始化
state = {
text: "父组件的文本",
hideChild: false
};
// 点击按钮,修改父组件文本的方法
changeText = () => {
this.setState({
text: "修改后的父组件文本"
});
};
// 点击按钮,隐藏(卸载)LifeCycle 组件的方法
hideChild = () => {
this.setState({
hideChild: true
});
};
render() {
return (
<div className="fatherContainer">
<button onClick={this.changeText} className="changeText">
修改父组件文本内容
</button>
<button onClick={this.hideChild} className="hideChild">
隐藏子组件
</button>
{this.state.hideChild ? null : <LifeCycle text={this.state.text} />}
</div>
);
}
}
ReactDOM.render(<LifeCycleContainer />, document.getElementById("root"));
需要首先声明的是,上图中的生命周期为V16.3版本, 再V16.4版本后React的生命周期又有少许微调,我们可以后面进行补充。
React 16 以来的生命周期也可以按照“挂载”“更新”和“卸载”三个阶段来看,在这个过程中,我将把 React 16 新增的生命周期方法,以及流程上相对于 React 15 产生的一些差异,进行比较。
Mounting 阶段:组件的初始化渲染(挂载)
在demo中的例子,依次打印的结果为
进入constructor
getDerivedStateFromProps方法执行
render方法执行
componentDidMount方法执行
从上图中不难看出,React 15 生命周期和 React 16.3 生命周期在挂载阶段的主要差异在于,废弃了 componentWillMount,新增了 getDerivedStateFromProps。
getDerivedStateFromProps 不是 componentWillMount 的替代品 事实上,componentWillMount的存在不仅“鸡肋”而且危险,因此它并不值得被“代替”,它就应该被废弃
而 getDerivedStateFromProps 这个 API,其设计的初衷不是试图替换掉 componentWillMount,而是试图替换掉 componentWillReceiveProps,因此它有且仅有一个用途:使用 props 来派生/更新 state
React 团队为了确保 getDerivedStateFromProps 这个生命周期的纯洁性,直接从命名层面约束了它的用途(getDerivedStateFromProps 直译过来就是“从 Props 里派生 State”)。所以,如果你不是出于这个目的来使用 getDerivedStateFromProps,原则上来说都是不符合规范的。
值得一提的是,getDerivedStateFromProps 在更新和挂载两个阶段都会“出镜”(这点不同于仅在更新阶段出现的 componentWillReceiveProps)。这是因为“派生 state”这种诉求不仅在 props 更新时存在,在 props 初始化的时候也是存在的。React 16 以提供特定生命周期的形式,对这类诉求提供了更直接的支持。
认识 getDerivedStateFromProps
这个新生命周期方法的调用规则如下:
static getDerivedStateFromProps(props, state)
在使用层面,你需要把握三个重点。
- getDerivedStateFromProps 是一个静态方法
静态方法不依赖组件实例而存在,因此你在这个方法内部是访问不到 this 的
2.第二个重点,该方法可以接收两个参数:props 和 state,它们分别代表当前组件接收到的来自父组件的 props 和当前组件自身的 state
3.第三个重点,getDerivedStateFromProps 需要一个对象格式的返回值。如果你没有指定这个返回值,那么大概率会被 React 警告一番
getDerivedStateFromProps 的返回值之所以不可或缺,是因为 React 需要用这个返回值来更新(派生)组件的 state。因此当你确实不存在“使用 props 派生 state ”这个需求的时候,最好是直接省略掉这个生命周期方法的编写,否则一定记得给它 return 一个 null。
注意,getDerivedStateFromProps 方法对 state 的更新动作并非“覆盖”式的更新,而是针对某个属性的定向更新
Updating 阶段:组件的更新
React 15 与 React 16.3 的更新流程对比如下图所示:
注意,咱们前面提到 React 16.4 对生命周期流程进行了“微调”,其实就调在了更新过程的getDerivedStateFromProps 这个生命周期上。先来看一张 React 16.4+ 的生命周期大图
React 16.4 的挂载和卸载流程都是与 React 16.3 保持一致的,差异在于更新流程上:
1.在 React 16.4 中,任何因素触发的组件更新流程(包括由 this.setState 和 forceUpdate 触发的更新流程)都会触发 getDerivedStateFromProps;
2.而在 v 16.3 版本时,只有父组件的更新会触发该生命周期。
那么,我们为什么需要getDerivedStateFromPropsne ?
改变背后的第一个“Why”:为什么要用 getDerivedStateFromProps 代替 componentWillReceiveProps?
1.getDerivedStateFromProps 是作为一个试图代替 componentWillReceiveProps 的 API 而出现的
2.getDerivedStateFromProps不能完全和 componentWillReceiveProps 画等号,其特性决定了我们曾经在 componentWillReceiveProps 里面做的事情,不能够百分百迁移到getDerivedStateFromProps 里。
我们对以上两点展开说说: 1.关于 getDerivedStateFromProps 是如何代替componentWillReceiveProps 的,getDerivedStateFromProps 可以代替 componentWillReceiveProps 实现基于 props 派生 state。
2.至于它为何不能完全和 componentWillReceiveProps 画等号,则是因为它过于“专注”了。这一点,单单从getDerivedStateFromProps 这个 API 名字上也能够略窥一二。原则上来说,它能做且只能做这一件事。
getDerivedStateFromProps 这个 API,它相对于早期的 componentWillReceiveProps 来说,正是做了“合理的减法”。而做这个减法的决心之强烈,从 getDerivedStateFromProps 直接被定义为 static 方法这件事上就可见一斑—— static 方法内部拿不到组件实例的 this,这就导致你无法在 getDerivedStateFromProps 里面做任何类似于 this.fetch()、不合理的 this.setState(会导致死循环的那种)这类可能会产生副作用的操作。
因此,getDerivedStateFromProps 生命周期替代 componentWillReceiveProps 的背后,是 React 16 在强制推行“只用 getDerivedStateFromProps 来完成 props 到 state 的映射”这一最佳实践。意在确保生命周期函数的行为更加可控可预测,从根源上帮开发者避免不合理的编程方式,避免生命周期的滥用;同时,也是在为新的 Fiber 架构铺路。
消失的 componentWillUpdate 与新增的 getSnapshotBeforeUpdate
getSnapshotBeforeUpdate(prevProps, prevState) {
// ...
}
这个方法和 getDerivedStateFromProps 颇有几分神似,它们都强调了“我需要一个返回值”这回事。区别在于 getSnapshotBeforeUpdate 的返回值会作为第三个参数给到 componentDidUpdate。它的执行时机是在 render 方法之后,真实 DOM 更新之前。在这个阶段里,我们可以同时获取到更新前的真实 DOM 和更新前后的 state&props 信息。
对于这个生命周期,需要重点把握的是它与 componentDidUpdate 间的通信过程。在 Demo 中我给出了一个使用示例,它将帮助你更加具体地认知这个过程。代码如下:
// 组件更新时调用
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log("getSnapshotBeforeUpdate方法执行");
return "haha";
}
// 组件更新后调用
componentDidUpdate(prevProps, prevState, valueFromSnapshot) {
console.log("componentDidUpdate方法执行");
console.log("从 getSnapshotBeforeUpdate 获取到的值是", valueFromSnapshot);
}
现在我们点击 Demo 界面上“修改子组件文本内容”按钮,就可以看到这两个生命周期的通信效果
getSnapshotBeforeUpdate方法执行
componentDidUpdate方法执行
从 getSnapshotBeforeUpdate 获取到的值是 haha
值得一提的是,这个生命周期的设计初衷,是为了“与 componentDidUpdate 一起,涵盖过时的 componentWillUpdate 的所有用例”(引用自 React 官网)。getSnapshotBeforeUpdate 要想发挥作用,离不开 componentDidUpdate 的配合
Unmounting 阶段:组件的卸载
卸载阶段的生命周期与 React 15 完全一致