学习使用Clock
组件,来重用和封装。并设置定时器。
封装了一个定时器,如:
function Clock(props) {
return (
Hello, world!
It is {props.date.toLocaleTimeString()}.
);
}
function tick() {
ReactDOM.render(
,
document.getElementById('root')
);
}
setInterval(tick, 1000);
Try it on CodePen.
以上代码忽略了一个关键的需求,就是设置定时器和每秒更新UI内容。理想情况下,我们希望只编写一次代码,然后使用定时器来更新内容。
ReactDOM.render(
,
document.getElementById('root')
);
如果想实现这个,我们需要在Clock
的组件添加state
。state
是一个参数,但是他是私有的并且由组件全权控制。
把函数转换成类
举个例子,可以把Clock
组件转换成类,只需要以下五步。
- 1.创建一个同名的ES6类,并且使用
extends React.Component.
- 2.添加一个空的方法到
render()
里面。 - 3.把函数的内容放到
render()
方法里。 - 4.替换
render()
方法里的参数名
为this.参数名
- 5.删除剩余的空函数声明。
class Clock extends React.Component {
render() {
return (
Hello, world!
It is {this.props.date.toLocaleTimeString()}.
);
}
}
Clock
现在被定义成一个类。这样我们就可以对组件添加额外的特性,如当前状态和生命周期。
添加本地state
到类中
把date
从参数到state
只需要3步。
- 1.在
render()
中替换this.参数.date
成this.state.date
class Clock extends React.Component {
render() {
return (
Hello, world!
It is {this.state.date.toLocaleTimeString()}.
);
}
}
- 2.添加一个类构建方法到来创建
this.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()}.
);
}
}
如何把对象传给父类的构建器,如:
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')
);
Try it on CodePen.
之后添加个定时器就可以运作了。
在类中添加一个生命周期方法
我们需要管理一个组件的生命周期,尤其再不使用组建后,需要释放使用的资源和空间。
当Clock
第一次渲染Dom的时候回在React中使用mounting
方法,释放的时候会使用unmounting
方法。我们可以在这两个特殊的点里添加特有的方法。
如下
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
//第一次启动 如IOS的viewDidLoad
}
componentWillUnmount() {
//释放 如IOS的dealloc
}
render() {
return (
Hello, world!
It is {this.state.date.toLocaleTimeString()}.
);
}
}
这个方法就是生命周期。
componentDidMount()
这个方法会在组件输出渲染dom后使用。这个时候可以设置一个定时器。
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
然后在组件释放前,释放不使用的定时器,如
componentWillUnmount() {
clearInterval(this.timerID);
}
最后需要实现tick()
方法内容,来执行定时器任务。
如下:
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')
);
Try it on CodePen.
剖析上面的代码流程。
- 1.执行
ReactDOM.render()
后会运行组件
,运行后会调用构建函数constructor
,就会初始化this.state
对象并获取当前时间。 - 2.接着会运行组件
的render()
方法。由于React把组件的render()
绑定了ReactDOM.render()
。所以谁输出到界面上。 - 3.当组件
输出显示就会调用componentDidMount()
创建定时器,并每秒执行定时器里面的方法tick()
。 - 4.在
tick()
方法中,会设置this.state
这个对象,并且获取当前时间,React会被告知状态修改,并进行执行render()
。当render()
执行后,会通知ReactDOM.render
并刷新。 - 5.当把组件
移除会执行componentWillUnmount
并释放定时器。
整个生命周期完毕。
使用当前的state
关于setState()
,有三样东西需要注意。
- 不能直接赋值
this.state
//错误的做法
this.state.comment = 'Hello';
需要使用
// Correct
this.setState({comment: 'Hello'});
只有在构建方法中才能直接赋值。
-
state
的更新尽可能异步进行
React 为了性能,可能在一个的更新里批量处理多次的setState() 调用。
因为this.props
和this.state
可能异步更新了,你不应该依赖他们的值来计算下一个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: []
};
}
当你需要更新时,可以对state里的对象分开回调,如
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作为props传递给他们的子组件中:
这同样适用用户定义的组件中:
FormattedDate组件将会接收data作为他的props并且将不知道他是来自哪,是Clock's state,是来自Clock's state, 还是来自手动输入的。
这就是我们平常所说的从上往下或者单向数据流。任何的state都是属于一些特定的组件,并且任何的数据或者UI视图 只能影响在他组件树下面的的组件。
如果你把一个组件的props想象成是瀑布,每一个组件的state就像一个额外的水资源,并且这在任意点处链接还往下流。
为了展示所有的组件都是孤立的,我们创建一个App组件来渲染三个
每个Clock都会独立设置以及更新自己的定时器。
在React app里,无论一个stateful or stateless的组件都被认为组件独立的细节都可能随着时间而改变。
你能用stateless组件代替stateful组件,反之亦然。