4.State and Lifecycle(状态和生命周期)

React版本:15.4.2
**翻译:xiyoki **

考虑前一节中滴答作响的时钟的例子。
到目前为止,我们只学习了一种更新UI的方法。
我们调用ReactDOM.render()来改变渲染的输出。

function tick() {
  const element = (
    

Hello, world!

It is {new Date().toLocaleTimeString()}.

); ReactDOM.render( element, document.getElementById('root') ); } setInterval(tick, 1000);

在本节中,我们将学习如何使Clock组件真正可重用和封装。它将设置自己的计时器,并每秒更新一次。
我们可以从封装时钟的结构开始:

function Clock(props) {
  return (
    

Hello, world!

It is {props.date.toLocaleTimeString()}.

); } function tick() { ReactDOM.render( , document.getElementById('root') ); } setInterval(tick, 1000);

但是,它漏掉了一项重要要求:Clock设置计时器和每秒更新UI都应当作为Clock自身的实现细节。
理想情况下,我们希望再写一次上述代码,并且Clock要更新它自身:

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

要实现这一点,我们需要将‘state’添加到Clock组件。
State和props比较相似,但state是组件私有的,并且完全由组件控制。
我们之前提到了定义为类的组件有一些额外的功能。
Local State是指:一个只对类组件有效的功能。

Converting a Function to a Class(将一个函数转换为类)

你只需5步就能将一个** functional component ** 转换成一个 ** class component ** :

  1. 用相同的名字创建一个ES6类,并且继承React.Component
  2. 将一个叫做 render() 的空方法添加到类中。
  3. 将函数体移动到render()方法中。
  4. render()方法体中用this.props替换 props。
  5. 删除空函数余下的声明。
class Clock extends React.Component {
  render() {
    return (
      

Hello, world!

It is {this.props.date.toLocaleTimeString()}.

); } }

Clock现在被定义成了一个类而不是函数。
这使我们可以使用额外的功能,如局部状态和生命周期钩子。

Adding Local State to a Class(将本地状态添加到类)

只需三步,我们就将date从props移动到state:
1)在render()方法中用 this.state.date替换 this.props.date :

class Clock extends React.Component {
  render() {
    return (
      

Hello, world!

It is {this.state.date.toLocaleTimeString()}.

); } }
  1. 添加一个 class constructor,用来指定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()}.

); } }

注意我们是怎样传递props到base constructor的:

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

类组件在调用base constructor时应当总是传递props。

  1. 元素上移除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') );

下面,我们将使Clock设置它自己的计时器,并且每秒钟更新自己。

Adding Lifecycle Methods to a Class(向类添加生命周期方法)

在具有许多组件的应用程序中,当组件被销毁时释放组件所占用的资源非常重要。
每当Clock被首次渲染到DOM时,我们都要建立一个定时器。这在React中被称作 "mounting(安装)"。
每当由Clock产生的DOM被移除时,我们也希望清除定时器。这在React中被称为 "unmounting(卸载)"。
我们可以在组件类上声明特殊的方法,以便在组件安装和卸载时运行一些代码:

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

    componentDidMount() {

    }

    componentWillUnmount() {

    }

  render() {
    return (
      

Hello, world!

It is {this.state.date.toLocaleTimeString()}.

); } }

这些方法被称为‘lifecycle hooks(生命周期钩子)。
在组件的输出(组件返回的React元素)已被渲染到DOM上后,componentDidMount()hook 运行。这是设置计时器的好地方:

componentDidMount() {
    this.timerID = setInterval(
       () => this.tick(),
      1000
      );
  }

注意我们是如何保存timerID到this上的。
虽然this.props由React元素自身设置,this.state也有其特殊的意义,然而如果你需要存储东西不用于可视化输出,那么你也可以自由地手动添加其他字段到类。
如果一些数据你没有在render()中使用,那么这些数据就不应该出现在state中。
我们将在componentWillUnmount() 生命周期钩子中清除计时器:

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

最后,我们将要实现每秒运行一次的tick()方法。
它将使用this.setState()去排定组件local 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 (
      

Hello, world!

It is {this.state.date.toLocaleTimeString()}.

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

现在时钟每秒滴答。
让我们快速回顾一下发生了什么以及方法的调用顺序:

  1. 被传递到ReactDOM.render(), React 调用Clock组件的constructor。 由于Clock需要展示当前的时间,它便用一个包含当前时间的对象初始化this.state。我们稍后会更新this state.

  2. 然后 React 调用 Clock组件的render()方法。 这就是React如何知道什么应该展现在屏幕上。然后 React 更新 DOM 去匹配Clock 的渲染输出。

  3. 当 Clock输出已被插入到 DOM时, React 调用componentDidMount()生命周期钩子函数。在该函数中,Clock组件让浏览器创建一个定时器,并每秒调用 tick() 一次。

  4. 浏览器每秒调用一次tick()方法。在该方法中,Clock组件通过传入一个包含当前时间的对象调用setState(),以此来安排UI更新。多亏了setState()调用, React 知道 state 已发生改变,并且再次调用 render()方法来了解什么应该出现在屏幕上。这次,render()方法中的this.state.date将会不同,并且输出的渲染将包含更新过的时间。React 因此更新了 DOM 。

  5. 如果Clock组件被从DOM中移除, React 就调用componentWillUnmount()生命周期钩子函数,因此定时器就被停止了。

Using State Correctly(正确使用State)

关于setState(),你应该知道三件事。

Do Not Modify State Directly(不要直接修改state)

例如:这将不会重新渲染组件:

// Wrong
this.state.comment = 'Hello';

相反,使用setState()代替:

// Correct
this.setState({comment: 'Hello'});

唯一你可以指派this.state的地方是constructor

State Updates May Be Asynchronous (状态更新可能异步)

为了性能,React会将多个setState()调用放置到单一的更新中。
由于this.propsthis.state可以被异步更新,因此你不应该依赖它们的值来计算下一个状态。
例如,此代码可能无法更新计数器:

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

要修复它,使用setState()的第二种形式,它接收一个函数,而不是一个对象。该函数将接收先前的state作为第一个参数,并将更新时的props作为第二个参数:

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

上面我们使用了一个箭头函数,但也可以使用常规函数:

// Correct
this.setState(function(prevState, props) {
  return {
    counter: prevState.counter + props.increment
  };
});
State Updates are Merged(状态更新被合并)

当你调用setState()时,React将你提供的对象合并到当前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})使this.state.posts 原封不动 , 但却完全替换了 this.state.comments

The Data Flows Down(数据向下流动)

不论是父组件,还是子组件都不知道某个组件是stateful还是stateless,并且它们不应该关心该组件是否被定义为函数或类。
这就是为什么状态通常被称为局部或封装。组件的状态除了拥有和设置它的组件外,其它任何组件都不能访问它。
组件可以选择将其state当做props向下传递给其子组件:

It is {this.state.date.toLocaleTimeString()}.

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


FormattedDate组件将在其props中接收 date ,并且不知道date是来自于Clock的state, 还是Clock的 props, 亦或是手动声明的:

function FormattedDate(props) {
  return 

It is {props.date.toLocaleTimeString()}.

; }

这通常被称为‘自顶向下’或‘单向’数据流。任何state总是由一些特定组件所拥有,并且从该state派生的任何数据或UI只能影响树中位于它们下面的组件。
如果你将组件树想象成由props构成的瀑布,那么每个组件的state就像一个额外的水源,它在任意点连接瀑布,并且也向下流。
为了展示所有组件是真正孤立的,我们可以创建一个App组件,该组件渲染三个

function App() {
  return (
    
); } ReactDOM.render( , document.getElementById('root') );

每个Clock独立地设置其自己的计时器,独立地更新。
在React应用程序中,组件是stateful还是stateless被视为可能随时间更改的组件的实现细节。你可以在stateful组件内使用stateless组件,反之亦然。

你可能感兴趣的:(4.State and Lifecycle(状态和生命周期))