React State and Lifecycle
如何更新当前组件上的状态,或者说如何在组件state发生变化时,将变化之后的UI显示在界面上。
直接通过函数的方式
我们可以通过如下方式来更新UI;设置一个时间监听器,每隔固定的时间去执行一次更新函数。
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(
element,
document.getElementById('root')
);
}
setInterval(tick, 1000);
使用自定义组件的方式
接下来将使用组件的形式来实验,先看代码部分。
- 定义一个用于更新的函数function tick(){}
,
- 在ReactDOM.render
中更新组件状态
function Clock(props) {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {props.date.toLocaleTimeString()}.</h2>
</div>
);
}
function tick() {
ReactDOM.render(
<Clock date={new Date()} />,
document.getElementById('root')
);
}
setInterval(tick, 1000);
可以看到以上两种方式其实实际上在我们更新的地方,已经真实的抽离出来的组件,他们之间的耦合度还是比较高;我们希望Clock
组件专门用来更新,在需要改组件的地方,直接引用而不需要传入props
将function转为class
我们可以通过如下步骤来实现
- 创建一个
ES6
class,使用同样的名字;但是需要继承自React.Component
- 在创建的勒种新建一个新的
render()
方法 - 将function中的函数体,移动到
render()
方法中 - 将
props
替换为this.props
经过以上步骤,我们就讲原本是函数的Clock
重新定义为了Class
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
给自定义组件添加state
- 将
this.props
修改为this.state
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>// this line
</div>
);
}
}
- 在构造函数中添加
this.state = {date: new Date()};
- 在这里需要注意一点,必须要有
super(props)
,用于将props向上传递给父组件
- 在这里需要注意一点,必须要有
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()}; // add this line
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
- 将
ReactDOM.render()
中的<Clock date={new Date()} />,
替换为<Clock />,
<Clock />,
- 最终代码
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {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')
);
添加组件的生命周期处理函数
- 当组件第一次被渲染在DOM中时,会调用
componentDidMount
我们在这里定义一个timer
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
- 自定一个处理
tick
的函数
tick() {
this.setState({
date: new Date()
});
}
- 当组件不在被需要时,也就是从DOM树中被移除时,调用
componentWillUnmount
我们在这里讲timer进行移除
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
通过以上步骤的改造,效果图如下
再次快速回顾下
- 当
<Clock/>
传入到ReactDOM.render()
方法时,React会调动Clock组件的构造方法,在构造函数中,初始化了this.state
并且在接下来的步骤中会使用到 - React接着会调动
Clock
组件的render()
方法,render()
方法才真正的可以让react知道需要在屏幕上显示什么,然后React更新DOM用于匹配Clock
的render函数的输出 - 当
Clock
组件根据state输出的结果被插入到DOM中时,React会调用componentDidMount()
方法,在该方法中,Clock
组件向Browser请求设置一个timer,该timer用来每隔1秒调用一次当前组件的tick()
方法 - 浏览器会按照一秒为单位区调用
tick()
方法。在Clock
组件中,使用setState()
并传入当前的最新事件来调度UI的更新。这里需要特别说明下setState()
,只有setState()
方法才可以让React知道当前当前的state更新了,React会根据新的状态来调用render()
方法更新UI。此时,render()
方法中this.state.date
就和之前的不同了,所以新的render方法会根据新的state来更新 - 如果
Clock
组件从DOM树种移除之后,React会调用该组件的componentWillUnmout()
方法,在这里我们需要停止timer
正确的处理state的更新
- 不要尝试直接更新state
// Wrong
this.state.comment = 'Hello';
- 要使用
setState
// Correct
this.setState({comment: 'Hello'});
- state的更新是异步的
- 如这里的在
setState
中进行运算可能拿不到预期的结构
- 如这里的在
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
- 相反,我么这里需要传入一个函数到
setSate
中,如下两种写法都正确
// Correct
this.setState(function(prevState, props) {
return {
counter: prevState.counter + props.increment
};
});
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
- state更新之后会覆盖掉之前的信息,state可以被单独更新
这个比较好理解。这里就不举例子
数据向下传递
一般来说,每一个组件的state都是该组件私有的,不会被外界知道,更不会暴露给外界;因此它是私密的。
一个组件可以将本组件的state传递到该组件的子组件中,并作为子组件的一个props
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
比如这里的我们将this.state.data.toLocaleTimeString()
这个当前<h2>
组件的state传入到其子组件,并作为其props
下面的例子应该更加可以说明这个问题
<FormattedDate date={this.state.date} />
function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}