React的最新大版本已经发到了17了,生命周期想相对于15的版本由于React采用Fiber架构,已经有了很大的变化,但是要了解React声明周期的变迁,我们还是要从React15说起
在 React 15 中,大家需要关注以下几个生命周期方法:
constructor()
componentWillReceiveProps()
shouldComponentUpdate()
componentWillMount()
componentWillUpdate()
componentDidUpdate()
componentDidMount()
render()
componentWillUnmount()
这些生命周期方法是如何彼此串联、相互依存的呢?这里我为你总结了一张大图:
我们采用如下demo
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: "子组件的文本" };
}
// 初始化渲染时调用
componentWillMount() {
console.log("componentWillMount方法执行");
}
// 初始化渲染时调用
componentDidMount() {
console.log("componentDidMount方法执行");
}
// 父组件修改组件的props时会调用
componentWillReceiveProps(nextProps) {
console.log("componentWillReceiveProps方法执行");
}
// 组件更新时调用
shouldComponentUpdate(nextProps, nextState) {
console.log("shouldComponentUpdate方法执行");
return true;
}
// 组件更新时调用
componentWillUpdate(nextProps, nextState) {
console.log("componentWillUpdate方法执行");
}
// 组件更新后调用
componentDidUpdate(preProps, preState) {
console.log("componentDidUpdate方法执行");
}
// 组件卸载时调用
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"));
这段代码渲染的结果为
可以看到,在组建的初始化阶段,打印依次为:
"进入constructor"
"componentWillMount方法执行"
"render方法执行"
"componentDidMount方法执行"
Mounting 阶段:组件的初始化渲染(挂载)
挂载过程在组件的一生中仅会发生一次,在这个过程中,组件被初始化,然后会被渲染到真实 DOM 里,完成所谓的“首次渲染”,componentWillMount、componentDidMount 方法同样只会在挂载阶段被调用一次 在挂载阶段,一个 React 组件会按照顺序经历如下图所示的生命周期:
注意 render 在执行过程中并不会去操作真实 DOM(也就是说不会渲染),它的职能是把需要渲染的内容返回出来。真实 DOM 的渲染工作,在挂载阶段是由 ReactDOM.render 来承接的。
componentDidMount 方法在渲染结束后被触发,此时因为真实 DOM 已经挂载到了页面上,我们可以在这个生命周期里执行真实 DOM 相关的操作。此外,类似于异步请求、数据初始化这样的操作也大可以放在这个生命周期来做(侧面印证了 componentWillMount 真的很鸡肋)。
Updating 阶段:组件的更新
组件的更新分为两种:一种是由父组件更新触发的更新;另一种是组件自身调用自己的 setState 触发的更新。这两种更新对应的生命周期流程如上图,从图中你可以明显看出,父组件触发的更新和组件自身的更新相比,多出了这样一个生命周期方法:componentWillReceiveProps(nextProps)
在这个生命周期方法里,nextProps 表示的是接收到新 props 内容,而现有的 props (相对于 nextProps 的“旧 props”)我们可以通过 this.props 拿到,由此便能够感知到 props 的变化。
从字面意义上理解:componentWillReceiveProps 很容易理解为父组件传入的props变化时候触发的,in fact,这个理解是错误的,准确来说
==如果父组件导致组件重新渲染,即便props没有改变,也会调用此方法== ----
++componentReceiveProps 并不是由 props 的变化触发的,而是由父组件的更新触发的,即便你修改了父组件中一个跟子组件无关的state,componentWillReceiveProps同样会执行!!!++
componentWillUpdate 会在 render 前被触发,它和 componentWillMount 类似,允许你在里面做一些不涉及真实 DOM 操作的准备工作;而 componentDidUpdate 则在组件更新完毕后被触发,和 componentDidMount 类似,这个生命周期也经常被用来处理 DOM 操作。此外,我们也常常将 componentDidUpdate 的执行作为子组件更新完毕的标志通知到父组件。
这里引出了React性能优化的一点: 采用shouldComponentdUpdate进行比较
shouldComponentUpdate(nextProps, nextState)
render 方法由于伴随着对虚拟 DOM 的构建和对比,过程可以说相当耗时。而在 React 当中,很多时候我们会不经意间就频繁地调用了 render。为了避免不必要的 render 操作带来的性能开销,React 为我们提供了 shouldComponentUpdate 这个口子。
React 组件会根据 shouldComponentUpdate 的返回值,来决定是否执行该方法之后的生命周期,进而决定是否对组件进行re-render(重渲染)。shouldComponentUpdate 的默认值为 true,也就是说“无条件 re-render”。在实际的开发中,我们往往通过手动往 shouldComponentUpdate 中填充判定逻辑,或者直接在项目中引入 PureComponent 等最佳实践,来实现“有条件的 re-render”。
Unmounting 阶段:组件的卸载
我们点击“隐藏子组件”后就可以把 LifeCycle 从父组件中移除掉,进而实现卸载的效果
这个生命周期本身不难理解,我们重点说说怎么触发它。组件销毁的常见原因有以下两个
1.组件在父组件中被移除了
2.组件中设置了 key 属性,父组件在 render 的过程中,发现 key 值和上一次不一致,那么这个组件就会被干掉。