生命周期(Life Cycle)
的概念应用很广泛,特别是在经济、环境、技术、社会等诸多领域经常出现,其基本涵义可以通俗地理解为“从摇篮到坟墓”(Cradle-to-Grave)
的整个过程
React
整个组件生命周期包括从创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、卸载等一系列过程
三个阶段
从图中可以看到,React
组件创建其实是有多个生命周期的,可以分成不同的阶段,主要分成三个阶段
Render 阶段
主要用于计算当前的一些状态
特点是: 纯净且不包含副作用。可能会被 React
暂停,中止或重新启动。
Pre-commit 阶段
声明是commit
呢? React
把当前状态映射到 DOM
的时候,去实际更新 DOM
节点。 在Pre-commit 阶段
还没有真正的更新 DOM
特点是:可以读取 DOM
Commit 阶段
这个阶段是 React 把状态都更新到真实的 DOM
节点上
特点是: 可以使用 DOM
,运行副作用,安排更新。
三个类型
创建时
当组件实例被创建并插入 DOM
中时,其生命周期调用顺序如下
constructor()
:一个组件的构造函数,组件更新到界面上之前会先调用constructor
- 用于初始化内部状态,很少使用
- 唯一可以直接修改
state
的地方
getDerivedStateFromProps()
:用于从外部的属性去初始化一些内部的状态- 当
state
需要从props
初始化时使用 - 尽量不要使用,维护
state/props
状态一致性会增加复杂度 - 每次
render
都会调用 - 典型场景: 表单控件获取默认值
- 当
render()
:组件必须定义的一个生命周期方法,用来描述DOM
结构componentDidMount()
: 用于数据请求,定义一些外部资源等等副作用- UI 渲染完成后调用
- 只执行一次
- 典型场景:获取外部资源
componentDidMount
如果我们将 AJAX 请求放置在生命周期的其他函数中,我们并不能保证请求仅在组件挂载完毕后才会要求响应。
如果我们的数据请求在组件挂载之前就完成,并且调用了 setState
函数将数据添加到组件状态中,对于未挂载的组件则会报错。
而在 componentDidMount
函数中进行 AJAX 请求则能有效避免这个问题。
更新时
当组件的 props
或 state
发生变化时会触发更新。组件更新的生命周期调用顺序如下
getDerivedStateFromProps()
:用于从外部的属性去初始化一些内部的状态shouldComponentUpdate(nextProps, nextState)
:告诉组件是否需要重新渲染,用于性能优化,比如判定指定props
发生改变,组件才进行重新渲染- 决定虚拟
DOM
是否需要重绘 - 一般可以由
PureComponent
自动实现 - 典型场景:性能优化
- 决定虚拟
render()
getSnapshotBeforeUpdate(prevProps, prevState)
- 在最近一次渲染输出(提交到 DOM 节点)之前调用,
state
已更新 - 与
componentDidUpdate
搭配使用 - 典型场景:捕获 render 之前的 DOM 状态
- 在最近一次渲染输出(提交到 DOM 节点)之前调用,
componentDidUpdate(prevProps, prevState)
- 每次 UI 更新时被调用
- 典型场景:页面需要根据
props
变化重新获取数据
shouldComponentUpdate
shouldComponentUpdate
允许我们手动地判断是否要进行组件更新,根据组件的应用场景设置函数的合理返回值能够帮我们避免不必要的更新。
卸载时
当组件从 DOM
中移除时,当组件从页面上消失时,需要进行销毁的时候
会调用如下方法:
componentWillUnmount()
: 做些资源释放,卸载副作用的事情
此方法中可以执行必要的清理操作,例如,清除 timer,取消网络请求或清除在
错误处理场景下需要用到生命周期
当渲染过程,生命周期,或子组件的构造函数中抛出错误时,会调用如下方法
static getDerivedStateFromError()
componentDidCatch()
常见使用场景
componentDidMount
+componentWillUnmount
实现定时器,如数字时钟
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = { date: new Date() };
}
componentDidMount() {
this.timerID = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date(),
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(<Clock />, document.getElementById("root"));
复制代码
getSnapshotBeforeUpdate(prevProps, prevState)
+componentDidUpdate()
捕获滚动条位置
有一个现实最新动态的列表-list,
- 每来一条新动态的时候都出现在 list 的最顶端
- list 支持滚动,以便用户查看历史消息
如果没有使用getSnapshotBeforeUpdate
那么每来一条消息,滚动条都会不停的往下滚动,导致无法固定在当前位置
因为每来一条消息都会在最顶部产生一个新的 DOM 节点,
为了避免这个问题,那我们就需要知道
- 在页面更新之前知道当前整个容器的高度,
- 在页面更新之后知道容器的新的高度
- 对比新旧高度的区别
- 每次页面更新完成后都将高度的差 加到容器的
scrollTop
上
这样就可以保证 即使有新消息的时候,滚动条也是固定在当前位置的
// SnapshotSample.less
.snapshotSample {
height: 100px;
padding: 20px;
overflow: auto;
border: 1px solid #eee;
widows: 300px;
}
复制代码
import React, { PureComponent } from "react";
import styles from "./SnapshotSample.less";
export default class SnapshotSample extends PureComponent {
constructor(props) {
super(props);
this.listRef = React.createRef();
this.state = {
list: [],
};
}
handleNewMessage() {
this.setState((prev) => ({
list: [`msg ${prev.list.length}`, ...prev.list],
}));
}
componentDidMount() {
for (let i = 0; i < 20; i++) this.handleNewMessage();
this.interval = setInterval(() => {
if (this.state.list.length > 200) {
clearInterval(this.interval);
return;
}
this.handleNewMessage();
}, 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
getSnapshotBeforeUpdate(prevProps, prevState) {
// 我们是否在 list 中添加新的 items ?
// 捕获滚动位置以便我们稍后调整滚动位置。
if (prevState.list.length < this.state.list.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// 如果我们 snapshot 有值,说明我们刚刚添加了新的 items,
// 调整滚动位置使得这些新 items 不会将旧的 items 推出视图。
// (这里的 snapshot 是 getSnapshotBeforeUpdate 的返回值)
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
render() {
return (
<div className={styles.snapshotSample} ref={this.listRef}>
{this.state.list.map((msg) => (
<div key={msg}>{msg}</div>
))}
</div>
);
}
}
复制代码
小结
- 理解 React 组件生命周期方法
- 理解生命周期的使用场景
最后
文章浅陋,欢迎各位看官评论区留下的你的见解!
觉得有收获的同学欢迎点赞,关注一波!