state属性和声明周期
到目前为止我们了解到一种方法改变UI界面ReactDOM.render(),改变渲染的输出。
看看一看之前的时钟例子
function tick() {
const element = (
Hello, world!
It is {new Date().toLocaleTimeString()}.
);
ReactDOM.render(
element,
document.getElementById('root')
);
}
setInterval(tick, 1000);
在这一篇里面我们将会学习怎么真正的封装一个时钟,并且时钟还会自动的更新时间。
这个过程比较的复杂,可以先从封装时钟外观开始
function Clock(props) {
return (
Hello, world!
It is {props.date.toLocaleTimeString()}.
);
}
function tick() {
ReactDOM.render(
,
document.getElementById('root')
);
}
setInterval(tick, 1000);
现在外观的样子是
我们现在想让之中自己去开启定时器,以后再用的时候直接创建一个时钟,他就可以自己运行起来,不用再最后我们添加一个定时器,每一秒调用一次tick函数。就想这样
ReactDOM.render(
,
document.getElementById('root')
);
开始动手吧
为了实现上面的效果,我们需要给组件添加一个属性state。state属性和props相似,不同的是state是一个私有属性,并且完全由组件控制
我们之前有提到过函数型的组件和类型组件相似,但是类型组件比函数型组件对一些功能,这里state就是这个多出来的功能。state只能在类型的组件中使用。
把函数型组件转化成类型组件
- 创建一个ES6的类,继承自React.Component
- 在类中添加一个空函数render()
- 把韩属性的组件中,函数体内容符合复制到render()里面
- 把render()里面的props改成this.props
- 删除函数型组件的声明
最终效果
class Clock extends React.Component {
render() {
return (
Hello, world!
It is {this.props.date.toLocaleTimeString()}.
);
}
}
现在我们就创建了一个时钟类,因为是类,所以我们可以给这个时钟里面添加state属性和声明周期钩子(lifecycle hooks)
给类添加state属性
我们需要把date属性从props移动到state中,需要三步
- 1 在render()方法中把this.props.date替换成this.state.date
class Clock extends React.Component {
render() {
return (
Hello, world!
It is {this.state.date.toLocaleTimeString()}.
);
}
}
- 2 在类中添加一个构造函数constructor构造函数顾名思义就是在创建各个累的时候自动调用的函数。在构造函数中添加一个属性state,并且附一个初始值。
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
Hello, world!
It is {this.state.date.toLocaleTimeString()}.
);
}
}
我们把props传进构造函数,并且类型的组件总是传递props到基本的构造函数,然后调用。
constructor(props) {
super(props);
this.state = {date: new Date()};
}
在构造函数中一定要先调用父类的构造函数
- 3 移除date属性从
元素
ReactDOM.render(
,
document.getElementById('root')
);
现在来看看整体效果
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
Hello, world!
It is {this.state.date.toLocaleTimeString()}.
);
}
}
ReactDOM.render(
,
document.getElementById('root')
);
后面我们将会来把定时器添加到类里面。
添加声明周期方法到类中
一个应用功能程序中有很多的组件,那么当组件被销毁的时候内存的释放就会非常重要。
先在需要在组件第一次渲染到UI界面上的时候开启一个定时器。需要在组件被销毁的时候清除定时器。这就需要用到生命周期的钩子函数
我们可以在类型组件中声明一些特殊的方法,这些方法会在组件被加载和被销毁的时候自动调用。
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
}
componentWillUnmount() {
}
render() {
return (
Hello, world!
It is {this.state.date.toLocaleTimeString()}.
);
}
}
上面代码中componentDidMount()方法会在组件被加载到Dom节点的时候被调用。我们可以再这里开启定时器
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
请注意我们在这里把timerID存进了this中
虽然props是有react自己创建,state有特殊的用处,我们还可以在类中自由的添加其他的属性不用于视觉输出。
如果某个属性没有出现在render()函数中,那么也就没有必要出现在state中。
我们需要在componentWillUnmount()中销毁定时器
componentWillUnmount() {
clearInterval(this.timerID);
}
最后我们写一个trick函数,这个函数将会每一秒调用一次。
并且使用this.setState()方法安排主键的更新
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 (
Hello, world!
It is {this.state.date.toLocaleTimeString()}.
);
}
}
ReactDOM.render(
,
document.getElementById('root')
);
总体效果
正确的是使用state
错误的使用方式
// Wrong
this.state.comment = 'Hello';
正确的使用方式
// Correct
this.setState({comment: 'Hello'});
唯一的一个使用this.state的地方是在构造方法里面。
state的更新可能是异步至此那个的
this.props和this.state可能是异步更新的,所以不能根据这两个属性计算新的值,例如
这是错的
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
正确的使用方式
把上一个状态值传进来
// 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中
例如state中会包含几个独立的变量
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
然后你可以分别更新这些属性
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会被替换
数据详向下传递
为什么state被称为本地的或者封装的数据,除了拥有和设置他的组件,其他的组件都不可能访问得到state
组件可以将state作为属性向下传递到子组件中
It is {this.state.date.toLocaleTimeString()}.
这的操作也可以用在用户自定义的组件中
FormattedDate组件可以接受date到自己的props中但是他不知道这个date到底来自于组件clock的state还是props或是手动输入的。
这样的数据传递称之为单向数据传递
任何state都会被一个组件拥有,任何被和这个state驱动的UI组件只有可能向下影响。
所有组件之间是独立互不影响的
function App() {
return (
);
}
ReactDOM.render(
,
document.getElementById('root')
);
每一个时钟都建立自己的timer,并且独立更新