React官网(3)—状态和生命周期

状态和生存周期

考虑上章时钟的例子,目前我们只是学习了更新UI的方式。

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

本节学习如何将Clock组件实现可重用和封装,它可以自己设置时间实现每秒的更新。

首先我们可以将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的内部实现。我们想要写一次然后自动更新。

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

为了实现这个功能,我们需要给Clock组件添加state特性。

state类似于props,但是它是私有且完全由组件控制的。我们之前提到将组件定义为类有一些额外的特性。Local state就是一种只有类才有的特性。

将函数转变为类

你可以使用五步将一个Clock这样的函数化组件转变为一个类组件。

  1. 使用ES6语法创建一个具有相同名字继承React.Component的类
  2. 添加一个空方法叫render()
  3. 将函数体移至render()方法内
  4. render()内的propsthis.props替换
  5. 删除掉剩余的函数声明部分
class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

Clock现在被定义为一个类,可以使用一些额外的特性例如Local state和Lifecycle hooks

给类添加一个local State

使用3步完成从props到state的转换

  1. this.state.date代替render()方法中的this.props.date

  2. 添加一个类的构造函数设置this.state的初始化值

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

    我们传递props给构造函数,类组件应该使用props调用构造函数

  3. <Clock />元素中删除date属性

接下来,我们实现每秒自动更新UI

给类添加生命周期方法

有很多组件组成的应用,在撤销组件时释放资源是很重要的。

我们想要实现当Clock组件第一次被渲染时设置一个计时器,这在React中叫做mounting;并且在由Clock创建的DOM结点移除时清空计时器,这在React中叫unmounting

我们可以通过在组件mount或unmount时添加特别的方法来完成

componentDidMount() {
}
componentWillUnmount() {
}

这些方法叫做‘生命周期钩子’

componentDidmount()方法在组件被渲染为DOM结点后调用。这是一个添加计时器的好地方。

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

this.props由React自己创建且this.state有特殊的含义时,你可以手动自己添加额外的字段来存储那些不用来视图输出的数据。

?如果你不在render()里面使用,它不会存在state里面

我们在componentWillUnmount()生命周期钩子函数中销毁计时器

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

最后,我们每秒实现tick()方法,使用this.setState()更新组件的状态。

tick() {
    this.setState({
      date: new Date()
    });
  }

快速回顾一下实现过程

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

2)随后React调用了Clock组件的render()方法.React通过这个方法知道什么应该输出在屏幕上。通过更新DOM树来匹配Clock的渲染输出。

3)当Clock输出嵌入DOM树里后,React调用componentDidMount()生存周期钩子。在这个函数里,React组件请求浏览器每秒调用tick()函数实现计时器。

4)浏览器每秒调用tick()方法。在这个方法里,Clock组件通过调用包含当前时间对象的setState()更新UI。多亏了setState()的调用,React知道state发生变化并调用render重新渲染UI。render()方法中的this.state.date不一样,render的输出就会包含已经更新的时间。

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可能是异步更新的,你不应该在更新下次状态时依赖于这些值。

例如下面这个代码并不会更新counter

// Wrong
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是合并更新的

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

数据流下行

不管是父组件还是子组件都不知道某个组件是有状态还是无状态的,并且他们也不关心组件是以函数或类的形式定义。

这就是为什么状态总是被叫做局部或者被封装的。并不是任何状态都是容易获得的,除非它属于某个组件。

一个组件可以向下传递状态作为props给它的子组件

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

也可以用于用户定义组件

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

这个FormattedDate组件接受date作为props并且并不清楚它是来自于Clock的状态、属性或自定义类型:

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

它一般被称为“从顶至下”或者“单向的”数据流。任何状态总是属于特定的组件,并且更新于state的数据或UI仅仅会影响树结构下面的组件。

如果将组件树想象成props的瀑布,每个组件的状态就像随机汇聚到此的水源,会随着一起向下传递。

为了表明所有组件都是真正独立的,我们可以创建一个App组件渲染三个clocks

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

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

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

在React app中,不论一个组件是有状态还是无状态的,它都可以被视为一个会随时间改变的组件的实现细节。你也可以在有状态组件里使用无状态组件,或者反着用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值