状态和生存周期
考虑上章时钟的例子,目前我们只是学习了更新UI的方式。
我们调用ReactDOM.render()来改变渲染输出。
本节学习如何将Clock组件实现可重用和封装,它可以自己设置时间实现每秒的更新。
首先我们可以将Clock封装成下面这样
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
设置计时器每秒更新UI应该作为Clock
的内部实现。我们想要写一次然后自动更新。
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
为了实现这个功能,我们需要给Clock
组件添加state特性。
state类似于props,但是它是私有且完全由组件控制的。我们之前提到将组件定义为类有一些额外的特性。Local state就是一种只有类才有的特性。
将函数转变为类
你可以使用五步将一个Clock
这样的函数化组件转变为一个类组件。
- 使用ES6语法创建一个具有相同名字继承
React.Component
的类 - 添加一个空方法叫
render()
- 将函数体移至
render()
方法内 - 将
render()
内的props
用this.props
替换 - 删除掉剩余的函数声明部分
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
Clock
现在被定义为一个类,可以使用一些额外的特性例如Local state和Lifecycle hooks
给类添加一个local State
使用3步完成从props到state的转换
用
this.state.date
代替render()
方法中的this.props.date
添加一个类的构造函数设置
this.state
的初始化值constructor(props) { super(props); this.state = {date: new Date()}; }
我们传递props给构造函数,类组件应该使用props调用构造函数
在
<Clock />
元素中删除date
属性
接下来,我们实现每秒自动更新UI
给类添加生命周期方法
有很多组件组成的应用,在撤销组件时释放资源是很重要的。
我们想要实现当Clock
组件第一次被渲染时设置一个计时器,这在React中叫做mounting;并且在由Clock
创建的DOM结点移除时清空计时器,这在React中叫unmounting。
我们可以通过在组件mount或unmount时添加特别的方法来完成
componentDidMount() {
}
componentWillUnmount() {
}
这些方法叫做‘生命周期钩子’
componentDidmount()
方法在组件被渲染为DOM结点后调用。这是一个添加计时器的好地方。
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
当this.props
由React自己创建且this.state
有特殊的含义时,你可以手动自己添加额外的字段来存储那些不用来视图输出的数据。
?如果你不在render()
里面使用,它不会存在state
里面
我们在componentWillUnmount()
生命周期钩子函数中销毁计时器
componentWillUnmount() {
clearInterval(this.timerID);
}
最后,我们每秒实现tick()
方法,使用this.setState()
更新组件的状态。
tick() {
this.setState({
date: new Date()
});
}
快速回顾一下实现过程
1)当<Clock />
传递给ReactDOM.render()
时,React调用了Clock组件的构造函数。因为Clock需要显示当前时间,它用一个包含当前时间的对象初始化了this.state
后面我们会更新这个state
2)随后React调用了Clock
组件的render()
方法.React通过这个方法知道什么应该输出在屏幕上。通过更新DOM树来匹配Clock的渲染输出。
3)当Clock输出嵌入DOM树里后,React调用componentDidMount()
生存周期钩子。在这个函数里,React组件请求浏览器每秒调用tick()
函数实现计时器。
4)浏览器每秒调用tick()
方法。在这个方法里,Clock组件通过调用包含当前时间对象的setState()
更新UI。多亏了setState()
的调用,React知道state
发生变化并调用render
重新渲染UI。render()
方法中的this.state.date
不一样,render的输出就会包含已经更新的时间。
5)如果Clock
组件从DOM树中被移除,React就会调用componentWillUnmount()
生存周期钩子函数,计时器就会停止
正确使用State
关于setState()
有三件事需要知道
不要直接更改State
下面这个例子不会重新渲染一个组件
// Wrong
this.state.comment = 'Hello';
相反,使用setState()
// Correct
this.setState({comment: 'Hello'});
唯一可以设置this.state
的地方是构造函数
状态更新可能是异步的
React也许会聚集一批setState()
调用而实现一次更新操作。
因为this.props
和this.state
可能是异步更新的,你不应该在更新下次状态时依赖于这些值。
例如下面这个代码并不会更新counter
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
为了避免这个问题,使用setState()
第二种形式接受一个函数而不是对象。这个函数会接受当前状态的前一个(目前为止最新的)state作为第一个参数和这个时刻更新的props作为第二个参数
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
上面我们使用了箭头函数,它和普通函数一样:
// Correct
this.setState(function(prevState, props) {
return {
counter: prevState.counter + props.increment
};
});
state是合并更新的
当你调用setState()
时,React合并了你要提供给当前状态的对象。
例如,你的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
数据流下行
不管是父组件还是子组件都不知道某个组件是有状态还是无状态的,并且他们也不关心组件是以函数或类的形式定义。
这就是为什么状态总是被叫做局部或者被封装的。并不是任何状态都是容易获得的,除非它属于某个组件。
一个组件可以向下传递状态作为props给它的子组件
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
也可以用于用户定义组件
<FormattedDate date={this.state.date} />
这个FormattedDate
组件接受date
作为props并且并不清楚它是来自于Clock
的状态、属性或自定义类型:
function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}
它一般被称为“从顶至下”或者“单向的”数据流。任何状态总是属于特定的组件,并且更新于state的数据或UI仅仅会影响树结构下面的组件。
如果将组件树想象成props的瀑布,每个组件的状态就像随机汇聚到此的水源,会随着一起向下传递。
为了表明所有组件都是真正独立的,我们可以创建一个App组件渲染三个clocks
function App() {
return (
<div>
<Clock />
<Clock />
<Clock />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
每个Clock设置自己的计时器并独立更新。
在React app中,不论一个组件是有状态还是无状态的,它都可以被视为一个会随时间改变的组件的实现细节。你也可以在有状态组件里使用无状态组件,或者反着用。