react state&生命周期
ps:本文大部分资料、内容来自react官方文档
在元素渲染中写的计时器,通过每秒执行一次ReactDOM.render()
来更新页面,但现在希望只写一次代码,只调用一次ReactDOM.render()
就能让<Clock />
组件自动更新,这需要在<Clock />
组件中添加state
.
一、实现步骤:
- 创建一个同名的 ES6 class继承
React.Component
。 - 添加一个空的
render()
方法。 - 将函数体移动到
render()
方法之中。 - 在
render()
方法中使用this.props
- 删除剩余的空函数声明。
1.改造<Clock />
组件
class Clock extends React.Component {
render() {
return (
<h2>当前时间:{this.props.date.toLocaleTimeString()}.</h2>
);
}
}
2.向 class 组件中添加局部的 state
- 把
render()
方法中的this.props.date
替换成this.state.date
- 添加一个 class 构造函数,然后在该函数中为
this.state
赋初值 - 移除
<Clock />
元素中的date
属性
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
<h2>当前时间:{this.state.date.toLocaleTimeString()}.</h2>
);
}
}
3.将生命周期方法添加到 Class 中
当Clocl
组件第一次被渲染的时候,为其设置一个计时器。这在react中被称为“挂载(mount)
”,同样,当Clock
组件在DOM中被删除时,应取消这个计时器。这在react中被称为“卸载(unmount)
”
可以在class组件中声明生命周期方法
,当组件被挂载或卸载时就会自动执行这些方法
class Clock extends React.Component {
...
componentDidMount(){
...
}
componentWillUnmount(){
...
}
...
}
componentDidMount()方法:
componentDidMount(){
this.timerID = setInterval
}
componentWillUnmount()方法:
componentWillUnmount() {
clearInterval(this.timerID);
}
在Clock
组件中添加一个timer()
方法,每秒调用一次,使用this.setState()
方法来每秒更新组件的state
最后代码:
class Clock extends React.Component {
constructor(props) {
super(props);
//为一开始显示的时间提供state.date
this.state = {date: new Date()};
}
componentDidMount() {
//每秒从timer()获取一次当前时间
this.timerID = setInterval(() => {
this.timer();
}, 1000);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
timer() {
this.setState({
//return中的h2需要的this.state.date从此处获取
date: new Date()
});
}
render() {
return (
//获取state中的date属性的值
<h2>当前时间:{this.state.date.toLocaleTimeString()}.</h2>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('react_test')
);
执行顺序:
- 当
<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
1.不要直接修改state
//错误的设置state姿势:
//此方式不会渲染到组件上
this.state.date = new Date();
//正确的state复制姿势:
//构造函数时唯一可用给this.state赋值的地方
this.state({ date: new Date() });
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.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
。
4.数据是向下流动的
不管是父组件或是子组件都无法知道某个组件是有状态的还是无状态的,并且它们也并不关心它是函数组件还是 class 组件。
这就是为什么称 state 为局部的或是封装的的原因。除了拥有并设置了它的组件,其他组件都无法访问。
组件可以选择把它的 state 作为 props 向下传递到它的子组件中:
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
这对于自定义组件同样适用:
<FormattedDate date={this.state.date} />
FormattedDate
组件会在其 props 中接收参数 date
,但是组件本身无法知道它是来自于 Clock
的 state,或是 Clock
的 props,还是手动输入的:
function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}
这通常会被叫做“自上而下”或是“单向”的数据流。任何的 state 总是所属于特定的组件,而且从该 state 派生的任何数据或 UI 只能影响树中“低于”它们的组件。
如果你把一个以组件构成的树想象成一个 props 的数据瀑布的话,那么每一个组件的 state 就像是在任意一点上给瀑布增加额外的水源,但是它只能向下流动。
为了证明每个组件都是真正独立的,我们可以创建一个渲染三个 Clock
的 App
组件:
function App() {
return (
<div>
<Clock />
<Clock />
<Clock />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
每个 Clock
组件都会单独设置它自己的计时器并且更新它。
在 React 应用中,组件是有状态组件还是无状态组件属于组件实现的细节,它可能会随着时间的推移而改变。你可以在有状态的组件中使用无状态的组件,反之亦然。