React 框架 Class 组件中 State 和 生命周期
在 Class 组件中 State 与 props 类似,但是 state 是私有的,并且完全受控于当前组件。
一、State 在组件中的使用
每次组件更新时组件的 render 方法都会被调用,但只要在相同的 DOM 节点中渲染此组件,就仅有一个此组件的 class 实例被创建使用。
所以如果我们想要修改组件的显示内容,需要使用如 state 或生命周期方法等很多其他特性。
1.1、State 的初始值
类组件中通过构造函数中使用 this.state 为 state 赋初始值。
constructor(props) {
super(props);
this.state = {date: new Date()};
}
Class 组件应该始终使用 props 参数来调用父类的构造函数。
二、类组件中的生命周期方法
在具有许多组件的应用程序中,当组件被销毁时释放所占用的资源是非常重要的。
2.1、Clock 示例
当 Clock 组件第一次被渲染到 DOM 中的时候,就为其设置一个计时器。这在 React 中被称为“挂载(mount)”。同时,当 DOM 中 Clock 组件被删除的时候,应该清除计时器。这在 React 中被称为“卸载(unmount)”。
我们可以为 class 组件声明一些特殊的方法,当组件挂载或卸载时就会去执行这些方法:componentDidMount、componentWillUnmount;这些就是生命周期方法。
2.2、componentDidMount 挂载方法
componentDidMount() 方法会在组件已经被渲染到 DOM 中后运行。
所以,最好在这里设置计时器:
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000 );
}
接下来把计时器的 ID 保存在 this 之中(this.timerID)。
尽管 this.props 和 this.state 是 React 本身设置的,且都拥有特殊的含义,但是其实你可以向 class 中随意添加不参与数据流(比如计时器 ID)的额外字段。
2.3、componentWillUnmount 卸载方法
我们会在 componentWillUnmount() 生命周期方法中清除计时器:
componentWillUnmount() {
clearInterval(this.timerID);
}
最后,我们会实现一个叫 tick() 的方法,Clock 组件每秒都会调用它。
2.4、Show the Code
使用 this.setState() 来时刻更新组件 state:
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')
);
让我们来快速概括一下发生了什么和这些方法的调用顺序:
- 当 <Clock /> 被传给 ReactDOM.render()的时候,React 会调用 Clock 组件的构造函数。因为 Clock 需要显示当前的时间,所以它会用一个包含当前时间的对象来初始化 this.state。我们会在之后更新 state。
- 之后 React 会调用组件的 render() 方法。这就是 React 确定该在页面上展示什么的方式。然后 React 更新 DOM 来匹配 Clock 渲染的输出。
- 当 Clock 的输出被插入到 DOM 中后,React 就会调用 ComponentDidMount() 生命周期方法。在这个方法中,Clock 组件向浏览器请求设置一个计时器来每秒调用一次组件的 tick() 方法。
- 浏览器每秒都会调用一次 tick() 方法。 在这方法之中,Clock 组件会通过调用 setState() 来计划进行一次 UI 更新。得益于 setState() 的调用,React 能够知道 state 已经改变了,然后会重新调用 render() 方法来确定页面上该显示什么。这一次,render() 方法中的 this.state.date 就不一样了,如此以来就会渲染输出更新过的时间。React 也会相应的更新 DOM。
- 一旦 Clock 组件从 DOM 中被移除,React 就会调用 componentWillUnmount() 生命周期方法,这样计时器就停止了。
三、正确地使用 State
关于 setState() 你应该了解三件事:
3.1、不要直接修改 State
例如,此代码不会重新渲染组件:// Wrong
this.state.comment = 'Hello';
而是应该使用 setState():// Correct
this.setState({comment: 'Hello'});
构造函数是唯一可以给 this.state 赋值的地方:
3.2、State 的更新可能是异步的
出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用。
因为 this.props 和 this.state 可能会异步更新,所以你不要依赖他们的值来更新下一个状态。
例如,此代码可能会无法更新计数器:// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
要解决这个问题,可以让 setState() 接收一个函数而不是一个对象。这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 做为第二个参数:
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
上面使用了箭头函数,不过使用普通的函数也同样可以:// Correct
this.setState(function(state, props) {
return {
counter: state.counter + props.increment
};});
3.3、State 的更新会被合并
当你调用 setState() 的时候,React 会把你提供的对象合并到当前的 state。
例如,你的 state 包含几个独立的变量:
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
然后你可以分别调用 setState() 来单独地更新它们:
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts });
});
fetchComments().then(response => {
this.setState({
comments: response.comments });
});
}
这里的合并是浅合并,所以 this.setState({comments}) 完整保留了 this.state.posts, 但是完全替换了 this.state.comments。