State&生命周期
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 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 类似,但是 state 是私有的,并且完全受控于当前组件。
将函数组件转换成class组件
通过以下五步将 Clock 的函数组件转成 class 组件:
- 创建一个同名的 ES6 class,并且继承于
React.Component。 - 添加一个空的
render()方法。 - 将函数体移动到
render()方法之中。 - 在
render()方法中使用this.props替换props。 - 删除剩余的空函数声明。
现在 Clock 组件被定义为 class,而不是函数。
每次组件更新时 render 方法都会被调用,但只要在相同的 DOM 节点中渲染 <Clock /> ,就仅有一个 Clock 组件的 class 实例被创建使用。这就使得我们可以使用如 state 或生命周期方法等很多其他特性。
向class组件中添加局部的state
我们通过以下三步将 date 从 props 移动到 state 中:
把
render()方法中的this.props.date替换成this.state.date:class Clock extends React.Component {render() {return (<div><h1>Hello, world!</h1><h2>It is {this.state.date.toLocaleTimeString()}.</h2></div>);}}
添加一个 class 构造函数,然后在该函数中为
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>);}}
通过以下方式将
props传递到父类的构造函数中:constructor(props) {super(props);this.state = {date: new Date()};}
Class 组件应该始终使用
props参数来调用父类的构造函数。移除
<Clock />元素中的date属性:ReactDOM.render(<Clock />,document.getElementById('root'));
代码如下:
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'));
将生命周期方法添加到Class中
在具有许多组件的应用程序中,当组件被销毁时释放所占用的资源是非常重要的。
当Clock组件第一次被渲染到 DOM 中的时候,就为其设置一个计时器。这在 React 中被称为“挂载(mount)”。
同时,当 DOM 中Clock组件被删除的时候,应该清除计时器。这在 React 中被称为“卸载(unmount)”。
我们可以为 class 组件声明一些特殊的方法,当组件挂载或卸载时就会去执行这些方法:class Clock extends React.Component {constructor(props) {super(props);this.state = {date: new Date()};}componentDidMount() {}componentWillUnmount() {}render() {return (<div><h1>Hello, world!</h1><h2>It is {this.state.date.toLocaleTimeString()}.</h2></div>);}}
这些方法叫做“生命周期方法”。
componentDidMount()方法会在组件已经被渲染到 DOM 中后运行,所以,最好在这里设置计时器:componentDidMount() {this.timerID = setInterval(() => this.tick(),1000);}
接下来把计时器的 ID 保存在
this之中(this.timerID)。
尽管this.props和this.state是 React 本身设置的,且都拥有特殊的含义,但是其实你可以向 class 中随意添加不参与数据流(比如计时器 ID)的额外字段。
我们会在componentWillUnmount()生命周期方法中清除计时器:componentWillUnmount() {clearInterval(this.timerID);}
最后,我们会实现一个叫
tick()的方法,Clock组件每秒都会调用它。
使用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'));
现在时钟每秒都会刷新。
让我们来快速概括一下发生了什么和这些方法的调用顺序:当
<Clock />被传给ReactDOM.render()的时候,React 会调用Clock组件的构造函数。 因为Clock需要显示当前的时间,所以它会用一个包含当前时间的对象来初始化this.state。我们会在之后更新 state。- 之后 React 会调用组件的
render()方法。这就是 React 确定该在页面上展示什么的方式。然后 React 更新 DOM 来匹配Clock渲染的输出。 - 当
Clock的输出被插入到 DOM 中后, React 就会调用ComponentDidMount()生命周期方法。在这个方法中,Clock组件向浏览器请求设置一个计时器来每秒调用一次组件的tick()方法。 - 浏览器每秒都会调用一次
tick()方法。 在这方法之中,Clock组件会通过调用setState()来计划进行一次 UI 更新。得益于setState()的调用,React 能够知道 state 已经改变了,然后会重新调用render()方法来确定页面上该显示什么。这一次,render()方法中的this.state.date就不一样了,如此以来就会渲染输出更新过的时间。React 也会相应的更新 DOM。 - 一旦
Clock组件从 DOM 中被移除,React 就会调用componentWillUnmount()生命周期方法,这样计时器就停止了。
