【译】快速起步-状态和生命周期

version: 15.4.2

状态和生命周期

到目前为止,我们仅学到了一种更新UI的方法。

我们通过调用 ReactDOM.render() 来改变渲染输出:

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

CodePen地址

在本节中,我们将学习如何使得 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);

CodePen地址

然而,它缺失了一个关键要求:在 Clock 组件的具体实现中,需要能创建它自己的定时器,并能每秒更新UI。

理想情况下,我们只需要写一次即可实现自更新的 Clock

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

想要实现这个目标,我们需要给 Clock 组件添加 state

stateprops 类似,但它是组件内部管理的的私有变量。

我们 之前提到,当使用 Class 定义组件时,可以使用一些附加的特性。本地状态就是这样的特性,仅在用 Class 定义组件时可用。

Function 转换为 Class

使用以下五步,可以将组件 Clock 从函数定义方式转换到类定义方式:

  1. 创建一个同名的继承自 React.ComponentES6 class

  2. 在类中添加空的 render()方法。

  3. 将函数体内的代码复制到 render() 方法中。

  4. render() 方法中使用 this.props 来替代 props

  5. 删除多余的空函数定义。

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

CodePen地址

现在 Clock 组件是用类定义的,而不是函数定义的。

这让我们可以使用一些附加特性,如本地状态和生命周期钩子。

添加本地状态到类

通过以下三个步骤,我们将会把 dateprops 移动到 state 中:

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

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

2) 添加 class constructor 并初始化 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()};
  }

类组件应始终传递 props 并调用基类构造函数。

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

CodePen地址

接下来,我们将使 Clock 启动自己的定时器,并每秒更新一次。

将生命周期方法添加到类

在包含多个组件的应用程序中,在组件释放时,销毁(清理)组件所占用的资源是非常重要的。

我们希望当 Clock 渲染到DOM时,第一时间 启动定时器。这个阶段在 React 中称之为 mounting

我们也希望当 Clock 产生的DOM被移除时 清除定时器 。这个阶段在 React 中称之为 unmounting

在组件的 mountingunmounting 阶段,我们可以定义特定的方法来运行我们的代码:

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

这些方法就被称之为生命周期钩子(lifecycle hooks)。

componentDidMount() 钩子在组件渲染到DOM之后运行,这是启动定时器的好地方:

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

注意我们是如何在 this 上保存定时器ID的

this.props 是由 React 自身管理的,this.state 也具有特定的意义。当您需要存储与渲染无关的内容时,也可以手动的在类中添加其他字段。

render() 无关的内容,没必要存储在 state 中。

我们将会在 componentWillUnmount 生命周期钩子中清除定时器:

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

最后,我们将实现每秒运行的 tick() 方法。

它将使用 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')
);

CodePen地址

现在 Clock 会每秒跳动。

我们来简单回顾下发生了什么以及调用方法的顺序:

1) 当 <Clock /> 传递给 ReactDOM.render() 时,React 调用了 Clock 组件的构造函数。由于 Clock 需要显示当前时间,我们通过一个包含当前时间的对象来初始化 this.state。稍后,我们将更新这个 state

2) 接下来,React 调用 Clock 组件的 render() 方法。这决定了 React 将会在屏幕上显示什么。接着,React 将组件的输出更新到DOM上。

3) 当 Clock 组件被添加到DOM之后,React会调用 componentDidMount() 钩子函数。 在它的内部, Clock 组件启动浏览器定时器,并每秒执行一次 tick()

4) 每一秒,浏览器都会调用 tick() 方法。在它的内部,Clock 组件将包含当前时间的对象传递给 this.setState(),并以此来调度UI变更。当调用 setState() 方法,React 知道了 state 变化,然后就调用 render() 来确定要在屏幕上渲染的内容。这个时候, render() 中的 this.state.date 将和之前不同,会产生包含当前时间的新的输出。于是,React则对应的更新DOM。

5) 当 Clock 组件从DOM移除时,React 将调用 componentWillUnmount() 来停止定时器。

正确的使用 state

关于 setState(),您需要知道以下三点:

不要直接修改 state

如下代码将不会重新渲染组件:

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

请使用 setState() 替代:

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

唯一可以给 this.state 赋值的地方是构造函数。

状态更新可能是异步的

为了提高性能,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
}));

以上代码我们使用了 arrow function ,但常规函数也是可以正常使用的:

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

状态是合并更新

当你调用 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

数据流向

父组件和子组件都不必要知道某个组件是有状态的还是无状态的,并且它们不应该关心它是否被定义函数或类。

这也是为什么 state 通常被称为本地状态或封装状态。因为除了组件本身之外,其他组件都是不能直接访问组件状态的。

父组件可以选择将状态通过子组件的属性进行传递:

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

This also works for user-defined components:

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

FormattedDate 组件将通过它自身的 props 接收到 date,不需要知道 date 是来自哪里(Clock 组件的状态或属性,乃至手动输入):

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

CodePen地址

这通常被称作 从上到下单向 数据流。任何 state 始终由某个特定组件拥有,并且从该 state 导出的任何数据或UI都只会影响子集。

如果把组件树想象为瀑布,每个组件的状态都是一个额外的水源,它可以从任一点汇入,但都向下流动。

为了表明组件都是相互隔离的,我们可以创建一个包含三个 Clock 组件的 App 组件:

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

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

CodePen地址

每个 React 有自己的定时器,并独立更新(并不会相互影响)。

React 应用中,组件有无状态被认为是可变的,它是组件的具体实现细节。你可以在有状态组件中使用无状态组件,反之亦然。

转载于:https://www.cnblogs.com/humin/p/6710105.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值