之前通过定时调用 ReactDOM.render()方法来更新元素。
现在来封装 Clock 组件,为它设置自己的计时器并每秒更新一次:
先封装外观:
function Clock(props) {
return (
<div>
<h1>Hello,world!</h1>
<h1>It is {props.date.toLocaleTimeString()}.</h1>
</div>
);
}
function tick() {
ReactDOM.render(<Clock date={new Date()} />, document.getElementById("root"));
}
setInterval(tick, 1000);
复制代码
接下来需要添加 state 来让组件自我更新。为了使用 state,需要将函数组件转换成 class 组件。
- 创建一个同名的 ES6 class,继承 React.Component。
- 添加一个空的 render()方法。
- 将函数体移动到 render()方法内。
- 在 render()方法中使用 this.props 替换 props。
- 删除剩余的空函数声明。
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello,world!</h1>
<h1>It is {this.props.date.toLocaleTimeString()}.</h1>
</div>
);
}
}
function tick() {
ReactDOM.render(<Clock date={new Date()} />, document.getElementById("root"));
}
复制代码
现在 Clock 是一个 class 组件。每次组件更新时 render 方法都会被调用,但只要在相同的 DOM 节点中渲染,就仅有一个 Clock 组件的 class 实例被创建使用。
然后添加局部 state
- 将 this.props.date 替换成 this.state.date
- 添加一个构造函数,在里面为 this.state 赋初值
- 使用构造函数将 props 传递给父类的构造函数
- 移除元素内的 date 属性,在构造函数中为 this.state 赋值新建日期
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"));
复制代码
添加生命周期方法
当 Clock 组件第一次被渲染到 DOM 中时,在 React 中称为挂载(mount)。
当 Clock 组件被删除时,在 React 中称为卸载。
我们需要在挂载时,为 Clock 添加一个计时器。在卸载时,清除计时器。
componentDidMount()会在组件已经被渲染到 DOM 后运行,所以在这里设置计时器最合适。
componentWillUnmount()会在组件即将被销毁前执行,所以在这里清除计时器。
最后实现 tick()方法,在里面使用 this.setState()来更新 state。
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 (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(<Clock />, document.getElementById("root"));
复制代码
注意这里的 this.state 变量名是固定的,不能像之前函数组件更改入参“props”那样把 this.state 更改成 this.XXX 其他名字。
这里的调用顺序是这样的:
- 被传给 ReactDOM.render 的时候,React 会调用 Clock 的构造函数来初始化 this.state。因为我们赋予一个时间对象,所以 this.state 初期值是当前的时间,但不会改变。
- React 调用 Clock 的 render 方法,更新 DOM 来进行 Clock 渲染的输出。
- 当 Clock 被渲染到 DOM 后,componentDidMount()方法被调用,初始化一个计时器。这个计时器每秒调用一次 tick()方法,tick()每次被调用都使用 this.setState()方法给 state 赋上新的时间。
- setState()被调用后,React 会得知 state 已经改变,然后重新调用 render()再次渲染。
- 当 Clock 被移除时,componentWillUnmount()方法被调用,清除计时器。
正确使用 state
- 不能直接修改 state
this.state.comment = 'Hello'; // 错误!
this.setState({comment: 'Hello'}); // correct
复制代码
构造函数是唯一可以直接赋值的地方。
- state 的更新可能是异步
处于性能考虑,React 可能会把多个 setState()调用合并成一个调用。
因为 this.props 和 this.state 可能会异步更新,所以不能依赖他们的值来更新下一个状态:
this.setState({
counter: this.state.counter + this.porps.increment,
});
// 可能会无法更新state
复制代码
要解决这个问题,可以让 setState()接收一个函数而不是对象。这个函数用上一个 state 和此次更新被应用时的 props 作为参数:
this.setState((state, props) => ({
counter: state.counter + props.increment,
}));
复制代码
- 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: response.comments}并不会影响到 this.state.posts,但是替换了 this.state.comments。实现了增量更新的效果。
数据是向下流动的
不管是父组件还是子组件都无法知道某个组件是有状态还是无状态。state 是局部的,或者说封装的。除了拥有并设置了他的组件,其他组件都无法访问。
组件可以把它的 state 作为 props 传递到子组件中:
function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}
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 (
<div>
<h1>Hello, world!</h1>
<FormattedDate date={this.state.date} />
</div>
);
}
}
ReactDOM.render(<Clock />, document.getElementById("root"));
复制代码
这叫自上而下的、单向的数据流。任何 state 只属于特定的组件,从该 state 派生的数据或 UI 只能影响树中低于它的组件。
将组件构成的树想像成瀑布,每个组件的 state 就像在那一处增加了额外的水源,只能向下流动。(有空的话画幅画)
每个组件都是真正独立。如果一次渲染三个 Clock 组件,它们会单独设置自己的计时器:
function App() {
return (
<div>
<Clock />
<Clock />
<Clock />
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
复制代码