008-状态和生命周期

一、概述

时钟示例

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);

如何使Clock组件真正可重用和封装。它会设置自己的计时器并每秒更新一次。

从封装时钟的外观开始:

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

 理想情况下,我们想写一次,并有Clock更新本身:

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

为了实现这个,我们需要给组件添加“state” Clock

状态类似于props,但是它是私人的并且完全由组件控制。

定义为类的组件具有一些附加功能。本地状态就是这样:一个仅适用于类的功能。

1.1、将函数转换为类

您可以通过Clock五个步骤将功能组件转换为类:

  1. 创建一个扩展名为ES6的类,名称相同,继承自React.Component

  2. 为它添加一个空的方法render()

  3. 将函数的主体移到render()方法中。

  4. 更换propsthis.propsrender()身体。

  5. 删除剩余的空函数声明。

class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

Clock 现在被定义为一个类而不是一个函数。

这让我们可以使用附加功能,例如本地状态和生命周期挂钩。

1.2、将本地状态添加到类

将date从props转为state主要有三步:

1、在该方法中替换this.props.date为:this.state.daterender()

class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

2、添加一个指定初始值的类构造函数this.state:【注意传递props给基础构造函数

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

3、date<Clock />元素中移除prop :

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')
);

1.3、将生命周期方法添加到类

在具有多个组件的应用程序中,释放组件在销毁时所占用的资源非常重要。

我们想要在第一次呈现给DOM 设置一个计时器Clock这在React中被称为“挂载”。

我们也想清除该定时器,只要Clock删除由该DOM生成的DOM 这在React中被称为“卸载”。

我们可以在组件类上声明特殊的方法来在组件装载和卸载时运行一些代码:

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.props由React自己设置并this.state具有特殊含义,但如果您需要存储不参与数据流的内容(如计时器ID),则可以手动将其他字段添加到类中。

我们将拆除componentWillUnmount()生命周期钩子中的计时器

componentWillUnmount() {
    clearInterval(this.timerID);
  }

最后,我们将实现一个调用的方法tick(),该Clock组件将运行每一秒。

它将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 (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

调用方法的顺序:

  1. <Clock />传递给ReactDOM.render()React ,React调用Clock组件的构造函数由于Clock需要显示当前时间,因此它会this.state使用包含当前时间的对象进行初始化我们稍后将更新这个状态。

  2. React然后调用Clock组件的render()方法。这就是React如何学习屏幕上应显示的内容。React然后更新DOM以匹配Clock渲染输出。

  3. Clock输出插入到DOM中时,React调用componentDidMount()生命周期钩子。在它里面,Clock组件要求浏览器设置一个计时器,tick()每秒调用一次该组件的方法。

  4. 浏览器每秒调用一次该tick()方法。在它内部,Clock组件通过调用setState()包含当前时间的对象来调度UI更新感谢setState()电话,React知道状态已经改变,并render()再次调用该方法来了解屏幕上应显示的内容。这一次,this.state.daterender()方法将会不同,因此渲染输出将包含更新的时间。React会相应地更新DOM。

  5. 如果Clock组件从DOM中移除,React将调用componentWillUnmount()生命周期钩子,以便定时器停止。

1.4、正确使用State

关于setState()需要知道3点

1、不要直接修改状态

// Wrong 不会重新渲染组件:
this.state.comment = 'Hello';
// Correct 使用setState():
this.setState({comment: 'Hello'});

唯一可以分配的地方this.state是构造函数。

2、状态更新可能是异步的

React可能会将多个setState()调用分批到单个更新中进行性能调优。

因为this.propsthis.state可异步更新,你不应该依赖于它们的值来计算下一个状态。

例如,此代码可能无法更新计数器:

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

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

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

我们使用了上面箭头函数,但它也适用于常规函数:

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

3、状态更新已合并

 当你调用setState()时,React将你提供的对象合并到当前状态。

 例如,您的状态可能包含多个独立变量:

  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})叶子this.state.posts完好无损,但完全取代this.state.comments

1.5、数据向下流动

不论是父组件还是子组件都不知道某个组件是有状态的还是无状态的,它们不应该关心它是被定义为一个函数还是一个类。 

这就是为什么状态通常被称为本地或封装。除了拥有和设置它的组件之外,其他任何组件都无法访问它。

组件可以选择将其状态作为道具传递给其子组件:

<h2>It is {this.state.date.toLocaleTimeString()}.</h2>

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

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

FormattedDate组件会收到date它的props,并不知道它是来自Clock stateClock props还是手工输入:

function FormattedDate(props) {
  return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}

这通常称为“自顶向下”或“单向”数据流。任何状态总是由某个特定组件拥有,并且从该状态派生的任何数据或UI只能影响树中“在其下”的组件。

如果您将组件树想象为props的瀑布,则每个组件的状态就像是一个额外的水源,它在任意点加入它,但是也会流下来。

为了显示所有组件都是真正隔离的,我们可以创建一个App呈现三个<Clock>组件

function App() {
  return (
    <div>
      <Clock />
      <Clock />
      <Clock />
    </div>
  );
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

每个Clock设置自己的计时器并独立更新。

在React应用程序中,无论组件是有状态的还是无状态的,都被视为可能随时间而改变的组件的实现细节。您可以在有状态组件内使用无状态组件,反之亦然。

 

posted @ 2018-05-02 16:39  bjlhx15  阅读(168)  评论(0编辑  收藏  举报
Copyright ©2011~2020 JD-李宏旭