React.js的状态和生命周期(二)

在上一篇文章:React.js的基础知识及一些demo(一)中,我们介绍了React.js的元素、JSX语法、组件和属性等相关基础语法及一些简单demo。这篇文章我们继续往下了解React的语法。

状态和生命周期

在上一篇文章更新已渲染的元素一节中,有一个时钟的例子。

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

在时钟例子中,我们通过调 ReactDOM.render() 方法来更新渲染的输出,这是一种更新UI的方式。
接下来我们将时钟功能封装成一个组件

 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 组件。state 和 props 类似,但是它是私有的,并且由组件本身完全控制。
在上一篇文章中提到,组件有两种定义方式:类组件和函数组件。用类定义的组件有一些额外的特性。 这个”类专有的特性”, 指的就是局部状态

如何将函数式组件转换为类组件

在上一小节中,我们定义的Clock组件属于函数式组件,我们以Clock为例,介绍函数组件转换为类组件。

  1. 创建一个继承自 React.Component 类的 ES6 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>
        );
      }
 }

Clock 现在被定为类组件,而不是函数式组件。类允许我们在其中添加本地状态(state)和生命周期钩子

在类组件中添加本地状态(state)

我们现在通过以下3步, 把date从属性(props) 改为 状态(state):
1.替换 render() 方法中的 this.props.date 为 this.state.date;
2.添加一个 类构造函数(class constructor) 初始化 this.state
3.移除 <Clock /> 元素中的 date 属性;
结果如下所示:

class Clock extends React.Component{
      constructor(props){
        super(props);//调用父类的constructor(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')
    );

这里:super关键字代表父类的实例(即父类的this对象)。
注意我们如何将 props 传递给基础构造函数,类组件应始终使用 props 调用基础构造函数。
接下来,我们将使 Clock 设置自己的计时器,并每秒更新一次。

在类中添加生命周期方法

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

Clock 第一次渲染到DOM时,我们要设置一个定时器 。 这在 React 中称为 “挂载(mounting)” 。

Clock 产生的 DOM 被销毁时,我们也想清除该计时器。 这在 React 中称为 “卸载(unmounting)” 。

当组件挂载和卸载时,我们可以在组件类上声明特殊的方法来运行一些代码,这些方法称为 “生命周期钩子”。
1.componentDidMount() 钩子在组件输出被渲染到 DOM 之后运行。这是设置时钟的合适位置;

  • 注意我们把计时器ID直接存在 this 中。
  • this.props 由 React 本身设定, 而 this.state 具有特殊的含义,但如果需要存储一些不用于视觉输出的内容,则可以手动向类中添加额外的字段。
  • 如果在 render() 方法中没有被引用, 它不应该出现在 state 中。

2.我们在componentWillUnmount()生命周期钩子中取消这个计时器;

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

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

关于这个例子的总结

我们来快速回顾一下该过程,以及调用方法的顺序:

1.当 <Clock /> 被传入 ReactDOM.render() 时, 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 得知了组件 state(状态)的变化, 随即再次调用 render() 方法,获取了当前应该显示的内容。 这次,render() 方法中的 this.state.date 的值已经发生了改变, 从而,其输出的内容也随之改变。React 于是据此对 DOM 进行更新。

5.如果通过其他操作将 Clock 组件从 DOM 中移除了, React 会调用 componentWillUnmount() 生命周期钩子, 所以计时器也会被停止。

使用 State(状态)的一些注意点

关于 setState() 有三件事是你应该知道的。
1.不要直接修改 state(状态)
例如,这样将不会重新渲染一个组件:

// 错误
this.state.comment = 'Hello';

应该使用 setState() 代替:

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

唯一可以分配 this.state 的地方是构造函数
2.state(状态) 更新可能是异步的
React 为了优化性能,有可能会将多个 setState() 调用合并为一次更新。
因为 this.props 和 this.state 可能是异步更新的,你不能依赖他们的值计算下一个state(状态)。
例如, 以下代码可能导致 counter(计数器)更新失败:

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

要解决这个问题,应该使用另一种 setState() 的形式,它接受一个函数而不是一个对象。这个函数将接收前一个状态作为第一个参数,应用更新时的 props 作为第二个参数:

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

3.state(状态)更新会被合并
当你调用 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(状态) 经常被称为 本地状态 或 封装状态的原因。 它不能被拥有并设置它的组件 以外的任何组件访问。

一个组件可以选择将 state(状态) 向下传递,作为其子组件的 props(属性):

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

FormattedDate 组件通过 props(属性) 接收了 date 的值,但它仍然不能获知该值是来自于 Clock的 state(状态) ,还是 Clock 的 props(属性),或者是直接手动创建的:

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

这通常称为一个“从上到下”,或者“单向”的数据流。任何 state(状态) 始终由某个特定组件所有,并且从该 state(状态) 导出的任何数据 或 UI 只能影响树中 “下方” 的组件。

如果把组件树想像为 props(属性) 的瀑布,所有组件的 state(状态) 就如同一个额外的水源汇入主流,且只能随着主流的方向向下流动。

要证明所有组件都是完全独立的, 我们可以创建一个 App 组件,并在其中渲染 3 个 <Clocks>:

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

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

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

在 React 应用中,一个组件是否是有状态或者无状态的,被认为是组件的一个实现细节,随着时间推移可能发生改变。你可以在有状态的组件中使用无状态组件,反之亦然。

参考:

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容