React JSWeb前端之路让前端飞

React学习教程(5)state&生命周期

2017-10-29  本文已影响17人  四冶读史

概述

目前,我需要在应用界面上实时显示日期(精确到秒),该如何处理?
按照React的规则,传递给组件的props对象只能读,而实时日期是实时更新的,那就只有实时的调用组件传递不同的props值,当然,这可以通过设置一个定时器实现,定时调用ReactDOM.render(),传递不同的参数给props来改变输出。如果只是这样,那用JavaScrip就能实现,并不能体现React的简洁与高效。

定时器实现

function tick() {
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toString('yyyy-MM-dd HH:mm:ss')}</h2>
    </div>
 );
  ReactDOM.render(
    element,
    document.getElementById('root')
 );
}
setInterval(tick, 1000);

每隔1s调用一次tick方法,获取改变后的元素,重新渲染。
如果仅仅这样的话,组件是没法重用的。
现将<h1>Hello, world!</h1>和<h2>It is {new Date().toString('yyyy-MM-dd HH:mm:ss')}</h2>分别抽象成一个展示欢迎的函数组件和一个展示时钟的组件:

function Welcome(props) {
    return <h1>hello, {props.name}</h1>;
}
function Clock(props){
    return <h2>It is {props.date.toString('yyyy-MM-dd HH:mm:ss')}</h2>;
}
function tick() {
  const element = (
    <div>
      <Welcome name= "world" />
      <Clock date={new Date()} />
    </div>
 );
  ReactDOM.render(
    element,
    document.getElementById('root')
 );
}
setInterval(tick, 1000);

从上可以看到,Welcome和Clock组件得到了复用。但是还不够好,因为:
1.ReactDOM.render()每秒都会执行一次,其实我只希望它执行一次,因为真正需要改变的是Clock组件;
2.Welcome组件每秒都会执行一次,但是它的内容无任何变化;
真正需要的是ReactDOM.render()只执行一次,时钟的变化交由Clock组件自身完成。

state实现

1.将Clock函数组件改成类组件

你可以通过5个步骤将函数组件 Clock 转换为类
==创建一个名称扩展为 React.Component 的ES6 类
==创建一个叫做render()的空方法
==将函数体移动到 render() 方法中
==在 render() 方法中,使用 this.props 替换 props
==删除剩余的空函数声明

class Clock extends Component{
    render(){
        return <h2>It is {this.props.date.toString('yyyy-MM-dd HH:mm:ss')}</h2>;
   }
}

2.给Clock类添加局部状态

在 render() 方法中使用 this.state.date 替代 this.props.date

class Clock extends Component{
    render(){
        return <h2>It is {this.state.date.toString('yyyy-MM-dd HH:mm:ss')}</h2>;
   }
}

3.添加一个类构造函数来初始化状态 this.state

class Clock extends Component{
    constructor(props) {
        super(props);
        this.state = {date: new Date()};
   }

    render(){
        return <h2>It is {this.state.date.toString('yyyy-MM-dd HH:mm:ss')}</h2>;
   }
}

注意我们如何传递 props 到基础构造函数的:

constructor(props) {
        super(props);
        this.state = {date: new Date()};
   }

类组件应始终使用props调用基础构造函数。

4.将定时器移到Clock组件本身

class Clock extends Component{
    constructor(props) {
        super(props);
        this.state = {date: new Date()};
   }​
    tick() {
        this.setState({
            date: new Date()
       });
   }
    render(){
        return <h2>It is {this.state.date.toString('yyyy-MM-dd HH:mm:ss')}</h2>;
   }
}

5.从 <Clock /> 元素移除 date 属性

const element = (
    <div>
        <Welcome name= "world" />
        <Clock />
    </div>
);
ReactDOM.render(
    element,
    document.getElementById('root')
);

6.给组件添加生命周期函数

class Clock extends Component{
    constructor(props) {
        super(props);
        this.state = {date: new Date()};
   }
    tick() {
        this.setState({
            date: new Date()
       });
   }
    componentDidMount(){
        //
   }
    componentWillUnmount(){
        //
   }
    render(){
        return <h2>It is {this.state.date.toString('yyyy-MM-dd HH:mm:ss')}</h2>;
   }
}

这些生命周期函数称作生命周期钩子。
当组件输出到 DOM 后会执行 componentDidMount() 钩子,即组件渲染到DOM后执行,此处一般用来加载数据的,但只执行一次,可以把定时器设置在此处:

componentDidMount(){
        // 装载定时器
        this.timerID = setInterval(
           () => this.tick(),
            1000
       );
   }

每秒执行一次tick,而tick方法则是通过this.setState更改局部状态date。
保存定时器ID,卸载时用到。
this.props由React本身设置,this.state具有特殊的含义,但如果需要存储不用于视觉输出的东西,则可以手动向类中添加其他字段。
如果你不在render()中使用某些东西,它就不应该在状态中。// 理论上是这样,但有时为了控制状态,也可以定义一些不用在render中使用的字段。
我们将在 componentWillUnmount()生命周期钩子中卸载计时器:

componentWillUnmount(){
        // 卸载定时器
        clearInterval(this.timerID);
   }

看看完整的代码:

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
function Welcome(props) {
    return <h1>hello, {props.name}</h1>;
}

class Clock extends Component{
    constructor(props) {
        super(props);
        this.state = {date: new Date()};
   }
    tick() {
        this.setState({
            date: new Date()
       });
   }
    componentDidMount(){
        // 装载定时器
        this.timerID = setInterval(
           () => this.tick(),
            1000
       );
   }
    componentWillUnmount(){
        // 卸载定时器
        clearInterval(this.timerID);
   }

    render(){
        return <h2>It is {this.state.date.toString('yyyy-MM-dd HH:mm:ss')}</h2>;
   }
}

const element = (
    <div>
        <Welcome name= "world" />
        <Clock />
    </div>
);
ReactDOM.render(
    element,
    document.getElementById('root')
);

此时,你可以看到浏览器每秒更新一次时钟。是不是很酷很神奇?
那这个过程是怎么实现的呢?
1.当<Clock />被传递给ReactDOM.render()时,React调用Clock组件的构造函数,由于Clock需要显示当前时间,所以使用包含当前时间的对象来初始化this.state。
2.React调用Clock组件的render()方法渲染屏幕,然后React更新DOM以匹配Clock的渲染输出。
3.当Clock的输出插入到DOM中时,React调用componentDidMount()生命周期钩子。在其中,Clock组件要求浏览器设置一个定时器,每秒钟调用一次tick()。
4.浏览器每秒钟调用tick()方法。 在其中,Clock组件通过使用包含当前时间的对象调用setState()来调度UI更新。 通过调用setState(),React 知道状态已经改变,并再次调用render()方法再次渲染屏幕。 而这次,render()方法中的this.state.date将不同,所以渲染输出将包含更新的时间,并相应地更新DOM。// 你会发现并不会再调用componentDidMount,因为该函数只在第一次装载的时候调用。
5.一旦Clock组件被从DOM中移除,React会调用componentWillUnmount()这个钩子函数,定时器也就会被清除。
从上可知Clock组件中3个方法的执行顺序:
constructor 组件调用时
render 组件调用时,state变化后调用
componentDidMount 组件装载后
componentWillUnmount 组件卸载后

正确地使用状态

状态(state)很灵活,也很实用,但使用时需要注意,不然,很有可能得不到想要的结果。
1.不要直接更新状态

this.state.date = new Date();

此时你可以看到,时钟并不会更新。
应该使用this.setState()函数。
构造函数是唯一能够初始化this.state的地方。
2.状态更新可能是异步的
React 可以将多个setState() 调用合并成一个调用来提高性能。
因为 this.props 和 this.state 可能是异步更新的,你不应该依靠它们的值来计算下一个状态。
例如,此代码可能无法更新计数器:

this.setState({
    counter: this.state.counter + this.props.increment,
});

要修复它,请使用第二种形式的 setState() 来接受一个函数而不是一个对象。 该函数将接收先前的状态作为第一个参数,将需要更新的值作为第二个参数:

this.setState((prevState, props) => ({
    counter: prevState.counter + props.increment
}));

上方代码使用了箭头函数,但它也适用于常规函数:

this.setState(function(prevState, props) {
  return {
        counter: prevState.counter + props.increment
 };
});

箭头函数后续会说到。
3.状态更新合并
当你调用 setState() 时,React 将你提供的对象合并到当前状态。
你可以调用 setState() 独立地更新它们。
数据自顶向下流动
父组件或子组件都不能知道某个组件是有状态还是无状态,并且它们不应该关心某组件是被定义为一个函数还是一个类。
这就是为什么状态通常被称为局部或封装。 除了拥有并设置它的组件外,其它组件不可访问。
组件可以选择将其状态作为属性传递给其子组件:

<h2>It is {this.state.date.toString('yyyy-MM-dd HH:mm:ss')}</h2>

这也适用于用户定义的组件:

<FormattedDate date={this.state.date} />

FormattedDate 组件将在其属性中接收到 date 值,并且不知道它是来自 Clock 状态、还是来自 Clock 的属性、亦或手工输入:

function FormattedDate(props) {
  return <h2>It is {this.state.date.toString('yyyy-MM-dd HH:mm:ss')}</h2>;
}

这通常被称为自顶向下或单向数据流。
任何组件状态由组件本身所有,并且只能传递到树中下方的组件。
为了表明所有组件都是真正隔离的,我们可以在element元素中同时渲染三个Clock:

const element = (
    <div>
        <Welcome name= "world" />
        <Clock />
        <Clock />
        <Clock />
    </div>
);

ReactDOM.render(
    element,
    document.getElementById('root')
);

每个 Clock 建立自己的定时器并且独立更新。
在React应用程序中,组件是有状态还是无状态被认为是可能随时间而变化的组件的实现细节。可以在有状态组件中使用无状态组件,反之亦然。

注:
本教程相关的所以源码,可在https://github.com/areawen2GHub/reacttest.git下载

参考地址:
https://react.bootcss.com/react/docs/state-and-lifecycle.html

上一篇下一篇

猜你喜欢

热点阅读