本文主要讲解 React 15 的生命周期,需要关注的有以下生命周期:
-
constructor()
-
componentWillReceiveProps()
-
shouldComponentUpdate()
-
componentWillMount()
-
componentWillUpdate()
-
componentDidUpdate()
-
componentDidMount()
-
render()
-
componentWillUnmount()
这些生命周期是如何进行串联以及相互依存的呢?
接下来我们会分挂载、更新和卸载三个阶段来对 React 15 的生命周期进行讲解。
挂载阶段(Mounting):组件的初始化渲染
在挂载阶段,组件实例会被创建并插入到 DOM 中。其生命周期调用顺序如下:
代码如下:
import React from "react";
export default class App extends React.Component {
constructor(props) {
super(props);
console.log("constructor()");
}
componentWillMount() {
console.log("componentWillMount()");
}
componentDidMount() {
console.log("componentDidMount()");
}
render() {
console.log("render()");
return (
<div>
<h1>Hello World!</h1>
</div>
);
}
}
constructor()
在组件挂载之前,会调用它的构造函数,只会被调用一次。
通常,在 React 中,构造函数仅用于以下两种情况:
- 通过给 this.state 赋值对象来初始化内部 state。
- 为事件处理函数绑定实例
constructor(props) {
super(props);
// 不要在这里调用 this.setState()
this.state = { counter: 0 };
// 替换做法:使用箭头函数
this.handleClick = this.handleClick.bind(this);
}
关于 constructor() 的更多介绍请访问官网文档。
componentWillMount()
这个方法同样也只会在挂载阶段调用且只会被调用一次。
componentWillMount() 在 render() 之前被调用,因此在此方法中同步 setState() 不会触发额外渲染。通常,建议使用 constructor() 来初始化 state。
避免在此方法中引入任何副作用或订阅。这些操作往往会伴随一些风险或者说不必要性。(具体原因我们将在以后讲解)。如果遇到这种情况,请改用 componentDidMount()。
关于 componentWillMount() 的更多介绍请访问官网文档。
render()
render() 的功能主要是 输出需要渲染的内容。需要注意的是 render() 在执行过程中并不会去操作真实 DOM,也就是说不会渲染。真实 DOM 的渲染,是在挂载阶段由 ReactDOM.render 来处理的。
另外重要的一点是 render() 应该为纯函数 ,这意味着在不修改组件 state 的情况下,每次调用时都返回相同的结果。
关于 render() 的更多介绍请访问官网文档。
componentDidMount()
componentDidMount() 在 render() 之后调用,此时真实 DOM 已经挂载到页面上,可以进行与真实 DOM 相关的操作,如网络请求、添加订阅等。
关于 componentDidMount() 的更多介绍请访问官网文档。
更新阶段(Updating):组件的更新
触发组件更新的方式有三种:
- 新的 props
- setState()
- forceUpdate() 而 forceUpdate() 官方不推荐使用且应该避免使用。所以组件的更新可以分为两种:
- 由父组件更新触发的更新 => 新的 props
- 组件自身触发的更新 => setState()
对应的生命周期流程如下图所示:
componentWillReceiveProps()
componentWillReceiveProps(nextProps);
此方法中,nextProps 表示的是新的 props,而现有的 props 可以通过 this.props 获取,从而可以比较两者的不同。
那么 componentWillReceiveProps() 什么时候会被触发呢?根据方法命名,认为 componentWillReceiveProps 是在组件的 props 内容发生了变化时被触发的。这种说法正确与否,揭晓前先看个示例。
import React from "react";
class Child extends React.Component {
constructor(props) {
super(props);
}
componentWillReceiveProps() {
console.log("componentWillReceiveProps");
}
shouldComponentUpdate() {
console.log("shouldComponentUpdate");
return true;
}
componentWillUpdate() {
console.log("componentWillUpdate");
}
componentDidUpdate() {
console.log("componentDidUpdate");
}
render() {
console.log("render");
return <div>随机数为:{this.props.count}</div>;
}
}
export default class App extends React.Component {
state = {
count: 0
};
handleClick = () => {
this.setState({
count: Math.round(Math.random() * 10)
});
};
render() {
return (
<div>
<button onClick={this.handleClick}>随机数</button>
<Child count={this.state.count} />
</div>
);
}
}
展示如图所示:
通过点击“随机数”按钮,来修改传入到 Child 组件中的 count 值。当 count 值发生改变时,此时一定会触发 componentWillReceiveProps()。生命周期方法调用顺序如下:
但如果我们在父组件中添加一个与 Child 组件无关的一个状态,改变这个状态后,会如何变化呢? 修改后的代码如下:
// 子组件代码没有改变
export default class App extends React.Component {
state = {
count: 0,
clickCount: 0
};
handleClick = () => {
this.setState({
count: Math.round(Math.random() * 10)
});
};
handleIncrement = () => {
this.setState({
clickCount: this.state.clickCount + 1
});
};
render() {
return (
<div>
<button onClick={this.handleClick}>随机数</button>
<button onClick={this.handleIncrement}>加1</button>
<div>与子组件无关的点击次数:{this.state.clickCount}</div>
<Child count={this.state.count} />
</div>
);
}
}
修改后的页面:
当点击“加1”按钮后,会发生什么呢?
可以看到,componentWillReceiveProps() 依然被触发。
官方文档中提到:
如果父组件导致组件重新渲染,即使 props 没有更改,也会调用此方法。如果只想处理更改,请确保进行当前值与变更值的比较。
通过示例和官方文档可以得出一个结论:
componentWillReceiveProps() 并不是由 props 的变化触发的,而是由父组件的更新触发的。
shouldComponentUpdate()
shouloadComponentUpdate(nextProps, nextState)
shouldComponentUpdate() 通常会作为性能优化的手段,根据其返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。
如果返回值为 false,则后续生命周期函数不会调用:componentWillUpdate()、render() 和 componentDidUpdate()。
我们也可以使用官方提供的 PureComponent。PureComponent 会对 props 和 state 进行浅层比较,并减少了跳过必要更新的可能性。
componentWillUpdate()
我们可以在此方法中执行一些不涉及真实 DOM 操作的工作。
componentDidUpdate()
componentDidUpdate(prevProps, prevState)
在组件更新完毕后被触发,和 componentDidMount() 用法类似,可以在此处对 DOM 进行操作。同样,也可以进行网络请求,但要主要比较更新前后的 props 值。
官方示例:
componentDidUpdate(prevProps) {
// 典型用法(不要比较 props):
if (this.props.userId !== this.props.userID) {
this.fetchData(this.props.userID);
}
}
关于 componentDidUpdate() 的更多介绍请访问官网文档。
卸载阶段(Unmounting):组件的卸载
卸载阶段就比较简单,只与一个生命周期有关:componentWillUnmount()。
componentWillUnmount()
componentWillUnmount() 会在组件卸载及销毁之前调用。在此方法中可以执行清理操作,比如 清除定时器 timer,取消网络请求、清除在 componentDidMount() 中创建的订阅以及重置状态等。
在此方法中不应该调用 setState(),因为该组件卸载后,将永远不会重新渲染。
总结
本篇文章中我们从 React 组件的三个阶段出发,分别介绍了各自阶段涉及到的生命周期和其用法。相信大家已经对 React 15 的生命周期有了清晰的认识。
而在 React 16 中,组件的生命周期发生了一些变化,有些不再推荐使用,又增加了一些新的生命周期。
在下篇文章中,将会详细介绍 React 16的生命周期。通过对比两者,解答出以下问题:
- 为什么 React 15 中的有些生命周期被官方标注为 UNSAFE?
- React 16 中新增的这些生命周期有什么考量?
如有错误,请指正。