什么是生命周期
在具有许多组件的应用程序中,当组件被销毁时释放所占用的资源是非常重要的。
组件从 被创建 到 被销毁 的过程称为组件的生命周期。
组件的生命周期可分成三个状态:
- Mounting(挂载时)
- Updating(更新时)
- Unmounting(卸载时)
组件的生命周期可分为三个阶段:
- Render: 用于计算当前的状态/更新信息,会根据产生的任务的优先级,安排任务的调度(schedule)
- Pre-commit: commit 之前,可以获取当前 DOM 的快照(snap)
- Commit: 把所有更新都 commit 到 DOM 树上
生命周期方法
图解
常见方法版
( 图片来源:projects.wojtekmaj.pl/react-lifec… )
完整方法版
( 图片来源:projects.wojtekmaj.pl/react-lifec… )
方法
– contructor
constructor(props);
复制代码
通常,在 React
中,构造函数仅用于以下两种情况:
- 通过给
this.state
赋值对象来初始化内部state
。( 唯一可以直接修改state
的地方) - 为事件处理函数绑定实例
在 constructor()
函数中不要调用 setState()
方法。
constructor(props) {
super(props);
// 不要在这里调用 this.setState()
this.state = { counter: 0 };
this.handleClick = this.handleClick.bind(this);
}
复制代码
– getDerivedStateFromProps
static getDerivedStateFromProps(props, state)
复制代码
这个方法是 “如何用 props
初始化 state
” 的最佳实践。
注意,该方法在每次 render 前都会被调用。
此方法适用于罕见的用例(表单控件获取默认值)。当 state
是从 props
获取时,就必须要维护两者的一致性,这将会增加复杂度和 bug
。
– shouldComponentUpate
shouldComponentUpdate(nextProps, nextState);
复制代码
当 props
或 state
发生变化时,shouldComponentUpdate()
会在渲染执行之前被调用。返回值默认为 true
。首次渲染或使用 forceUpdate()
时不会调用该方法。
此方法 仅作为性能优化 的方式而存在。它的工作一般可以由 PrueComponent 自动实现。
后续版本,
React
可能会将shouldComponentUpdate
仅视为提示,并且,当返回false
时,仍可能导致组件重新渲染。
– render
render();
复制代码
用于描述 DOM
结构,组件中唯一必须实现的方法。
– getSnapshotBeforeUpdate
getSnapshotBeforeUpdate(prevProps, prevState);
复制代码
能在组件发生更改之前从 DOM
中捕获一些信息(例如,滚动位置)。此生命周期方法的任何返回值将作为参数传递给 componentDidUpdate()
。
– componentDidMount
componentDidMount();
复制代码
在组件挂载后(插入 DOM
树中)立即调用。在这里可以安全操作 DOM
节点、发送ajax
请求(DOM
节点的初始化)或一些副作用的事情(订阅)
如果你在这里调用了 setState
,它将触发额外渲染,虽然此渲染会发生在浏览器更新屏幕之前,但会导致性能问题。
– componentDidUpdate
componentDidUpdate(prevProps, prevState, snapshot);
复制代码
在更新后会被立即调用。首次渲染不会执行此方法。
⚠️ 注意, 如果直接调用 setState()
,它必须被包裹在一个条件语句里,否则会导致死循环。
– componentWillUnmount
componentWillUnmount();
复制代码
在组件卸载及销毁之前直接调用,做一些资源释放操作,例如,清除 timer
,取消网络请求或清除在 componentDidMount()
中创建的订阅等。
⚠️ 注意:不应调用 setState()
,因为该组件将永远不会重新渲染。
演示
Clock
用官方的经典 Clock
组件,可以清楚的展示出常见生命周期方法的应用:
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);
}
componentDidUpdate() {
console.log("component did update!");
}
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"));
复制代码
在组件第一次挂载时,设置一个定时器;同时,在组件卸载时,清除定时器。
state.date
发生改变时,会引发组件的重新渲染。
getSnapshotBeforeUpdate 实践
以特殊方式处理滚动位置的聊天线程:因为每一条新消息的置顶设定,无法让页面固定在某处,所以在每次更新前都需要计算调整滚动条的位置。
class ScrollList extends PureComponent {
state = {
messages: [],
};
getSnapshotBeforeUpdate() {
return this.rootNode.scrollHeight;
}
componentDidUpdate(prevProps, prevState, prevScrollHeight) {
const scrollTop = this.rootNode.scrollTop;
if (scrollTop < 5) return;
this.rootNode.scrollTop =
scrollTop + (this.rootNode.scrollHeight - prevScrollHeight);
}
render() {
return (
<div className="scroll-list" ref={(n) => (this.rootNode = n)}>
{this.state.messages.map((msg) => (
<div>{msg}</div>
))}
</div>
);
}
}
复制代码
在 DOM
更新前,通过 getSnapshotBeforeUpdate
获取原来的元素内容高度( scrollHeight
),并作为第三个参数传给 componentDidUpdate
;
在 componentDidUpdate
中,通过添加元素内容高度差值( this.rootNode.scrollHeight - prevScrollHeight
),调整滚动条位置( scrollTop
)。
参考资料
- State & 生命周期:zh-hans.reactjs.org/docs/state-…
- React.Component:zh-hans.reactjs.org/docs/react-…